鏈表_LINKLIST,表_linklist
- 鏈表_LINKLIST
- 鏈表的結構
- 鏈表的基本操作
- 節點的插入_INSERTNODE
- 節點的刪除_REMOVENODE
- 訪問鏈表元素_VISITNODE
- 鏈表基本操作介面_INTERFACE
- 鏈表的編碼實現
- 鏈表總結
本文中的所有代碼都可以在這裡:
https://github.com/qeesung/algorithm/tree/master/chapter10/10-2
或者這裡:
http://download.csdn.net/detail/ii1245712564/8775457
找到
鏈表_LINKLIST
鏈表的按照儲存的方式分類可以分為兩種,一種是記憶體連續的順序鏈表,一種是記憶體不連續的鏈式鏈表,在本文中主要介紹更為靈活的鏈式鏈表,後文中出現的鏈表都預設為鏈式鏈表。
鏈表是一種實體儲存體單元上非連續、非順序的儲存結構,資料元素的邏輯順序是通過鏈表中的指標連結次序實現的。鏈表由一系列結點(鏈表中每一個元素稱為結點)組成,結點可以在運行時動態產生。每個結點包括兩個部分:一個是儲存資料元素的資料域,另一個是儲存下一個結點地址的指標域。
鏈表的結構
鏈表顧名思義就是一個表,表裡面的每一項item儲存一個資料,多個item串在一起就構成了鏈表
鏈表裡面的一個項item,item裡面裝著我們需要的資料:
將多個item串成一串,就構成了一個表結構:
在鏈表裡面,我們將這樣一個item稱為鏈表節點ListNode,那到底這些item是怎麼把彼此串起來的呢?我們來看一下ListNode內部的資訊
鏈表節點_LinkNode
鏈表的基本元素就是一個鏈表節點ListNode,也就是我們上面所說的一個item,鏈表節點中包含兩個資訊,一個是資料data,一個是指向下一個節點的指標next。
鏈表節點串連
注意ListNode資訊裡面有一個叫做next的指標,它的作用就是用於指向下一個節點,那這就好辦啦,我們可以將next指標指向下一個listNode,下一個listNode的next指標又可以指向其他的listNode,這樣我們從第一個鏈表節點開始,通過next指標就可以訪問所有的鏈表節點元素了!
鏈表的基本操作
鏈表有兩個基本的操作:
鏈表節點時資料的載體,插入元素就是插入一個節點,刪除元素就是刪除一個節點
節點的插入_INSERTNODE
假如現在我們要在鏈表的兩個節點Node1和Node2之間插入一個新的節點Node3。
可以大致分為下面幾個步驟:
- 儲存Node1指向Node2的指標,即儲存Next1
- 將Node1的Next指標指向新的節點Node3
- 將Node3的Next指標設為之前儲存起來的指向Node2的指標
該操作的已耗用時間為O(1)
節點的刪除_REMOVENODE
有插入就有刪除,假設有三個鏈表節點Node1,Node2,Node3,
現在需要將節點Node2刪除。
可以大致分為下面幾個步驟:
- 儲存Node2指向Node3的指標,即儲存Next2
- 將Next1指向的節點Node2刪除
- 把Next1賦值為之前儲存的指向Node3的指標
該操作的已耗用時間為O(1)
訪問鏈表元素_VISITNODE
對於一個鏈表,我們可以從鏈表的表頭開始訪問到鏈表裡面的任意一個元素,因為鏈表的每一個節點都有一個指向下一個節點的指標next。
假設一鏈表有三個節點Node1,Node2,Node3,表頭為Node1,現在我們需要訪問第三個節點的資料
這樣我們就找到了第三個節點,並取出其中的元素
鏈表基本操作介面_INTERFACE
list.h
#ifndef List_H#define List_H/** 鏈表可以自由的實現其他功能,這裡只列出了一小部分 */template <class Elem>class List{public: // 在鏈表頭插入一個元素 virtual bool insert(const Elem &) = 0; // 在鏈表尾插入一個元素 virtual bool append(const Elem &) = 0; // 移除鏈表裡面的一個元素 virtual bool remove(const Elem &) = 0; // 清空鏈表裡面額的所有元素 virtual void clear() = 0; // 列印整個鏈表 virtual void print() = 0;};#endif
鏈表的編碼實現
link_list.h
#ifndef LINK_LIST_H#define LINK_LIST_H#include "list.h"/** * 定義鏈表的節點 */template <class Elem>class ListNode {public: Elem data;// 實際的資料 ListNode<Elem> * next;// 指向下一個節點}; /** 子類化List,實現介面功能 */template <class Elem>class LinkList : public List<Elem>{private: ListNode<Elem> * header;// 鏈表的表頭 void freeList(ListNode<Elem> * startNode);//迭代釋放從start開始的所有節點public: // 建構函式和解構函式 LinkList(); ~LinkList(); // 基本的操作函數 // 在鏈表頭插入一個元素 bool insert(const Elem &); // 在鏈表尾插入一個元素 bool append(const Elem &); // 移除鏈表裡面的一個元素 bool remove(const Elem &); // 清空鏈表裡面額的所有元素 void clear(); // 列印整個鏈表 void print();};#include "link_list.cc"#endif
相對於List的介面,我們新添加了一個header指標指向鏈表的表頭,我們可以以這個指標為入口,找到鏈表中的所有元素!
link_list.cc
#include <iostream>// 建構函式和解構函式template <class Elem>LinkList<Elem>::LinkList(){ // 首先建立一個鏈表的表頭 header = new ListNode<Elem>(); header->next = NULL;}/** 迭代的釋放節點指標開始的所有節點 */template <class Elem>void LinkList<Elem>::freeList(ListNode<Elem> * startNode){ while(startNode != NULL) { ListNode<Elem> * tempNode = startNode->next; delete startNode; startNode = tempNode; } return;}template <class Elem>LinkList<Elem>::~LinkList(){ //遞迴的釋放整個鏈表 freeList(header);}// 基本的操作函數// 在鏈表頭插入一個元素template <class Elem>bool LinkList<Elem>::insert(const Elem & ele){ ListNode<Elem> * newNode = new ListNode<Elem>(); if(newNode == NULL) return false; newNode->next=NULL; newNode->data = ele; ListNode<Elem> * tempNode = header->next; header->next = newNode; newNode->next = tempNode; return true;}// 在鏈表尾插入一個元素template <class Elem>bool LinkList<Elem>::append(const Elem & ele){ // tempNode裡面現在就指向最後一個元素 ListNode<Elem> * newNode = new ListNode<Elem>(); if(newNode == NULL) return false; newNode->next = NULL; newNode->data = ele; // 首先找到鏈表末尾 ListNode<Elem> * lastNode = header; while(lastNode->next !=NULL) { lastNode = lastNode->next; } // 追加節點 lastNode->next = newNode; return true;}// 移除鏈表裡面的一個元素template <class Elem>bool LinkList<Elem>::remove(const Elem & ele){ // 在鏈表裡面找到對應的元素 ListNode<Elem> * targetNode = header; while(targetNode->next!=NULL && targetNode->next->data!=ele) { targetNode = targetNode->next; } if(targetNode->next == NULL)// 沒有找到目標元素 return false; ListNode<Elem> * tempNode = targetNode->next->next; delete targetNode->next; targetNode->next = tempNode; return true;}// 清空鏈表裡面額的所有元素template <class Elem>void LinkList<Elem>::clear(){ freeList(header->next); header->next = NULL;}// 列印整個鏈表template <class Elem>void LinkList<Elem>::print(){ ListNode<Elem> * tempNode = header; while(tempNode->next!=NULL) { std::cout<<tempNode->next->data<<"\t"; tempNode = tempNode->next; } std::cout<<std::endl;}
link_list_test.cc
#include <iostream>#include "link_list.h"int main(int argc, char const *argv[]){ LinkList<int> list; // insert three elements std::cout<<"Insert 1 2 3"<<std::endl; list.insert(1); list.insert(2); list.insert(3); list.print(); list.clear(); std::cout<<std::endl; // append three elements std::cout<<"append 1 2 3"<<std::endl; list.append(1); list.append(2); list.append(3); list.print(); list.clear(); std::cout<<std::endl; // append three elements then remove one of them std::cout<<"append 1 2 3 then remove 3"<<std::endl; list.append(1); list.append(2); list.append(3); list.remove(3); list.print(); list.clear(); std::cout<<std::endl; return 0;}
編譯方法:
g++ link_list_test.cc -o link_list_test
運行方法:
./link_list_test
運行結果:
Insert 1 2 3
3 2 1
append 1 2 3
1 2 3
append 1 2 3 then remove 3
1 2
鏈表總結
既然鏈表有這麼大的優勢,那麼以後直接就拋棄傳統的數組,就用鏈表吧!這樣當然不行,鏈表和數組之間各有各的優勢,各自有各自的使用情境:
- 鏈表:可以在常數時間O(1)之內完成對資料完成對資料的刪除添加操作,但是對資料的訪問操作的已耗用時間卻需要線上性時間O(n),鏈表的資料訪問是不支援直接存取的,因為鏈表的節點的資料不是順序分布的,在記憶體(一般在堆裡面)裡面看到的鏈表的資料分布可能是這樣的:
所以鏈表適用於對資料頻繁插入刪除的情形
- 數組:數組可以在常數時間O(1)內對資料進行訪問,因為數組的元素分布內在存中是連續的,可以直接數組起始地址加上一個位移量找到對應的元素,數組在記憶體裡面的部分可能是這樣的:
而如果要對資料進行刪除和添加,那麼已耗用時間為O(n),所以數組適用於對資料訪問比較頻繁的情形。