第一次學習線性表必定會立刻接觸到一種叫作順序表(順序存儲結構),通過上一篇的分析順序表的優缺點是很顯然的,它雖然可以很快的訪問讀取元素,可是在解決如插入和刪除等操做的時候,卻須要移動大量的元素,效率較低,那麼是否有一種方法能夠改善或者解決這個問題呢?ios
首先咱們須要考慮,爲何順序表中的插入刪除操做會涉及到元素的移動呢?算法
好傢伙,問題就是圍繞着順序表的最大的特色出現的——順序存儲,相鄰放置元素,也就是說每一個元素都是根據編號一個一個挨着的,這就致使了 插入或刪除後,爲了仍然呈順序線性存儲,被操做元素後面的元素的位置均須要發生必定的變化,你應該能想象獲得,在擁擠的隊伍中忽然從中插入一個學生的場景,後面浩浩蕩蕩的人羣,口吐芬芳的向後挪了一個空位,若是人羣過大,從新排好隊也須要必定的時間編程
好嘛,人與人之間別這麼擠在一塊兒,每一個人與人之間都流出一點空隙來,留必定的位置出來,好了,這好像是個辦法,可是負責一個一個與學生交流填表的老師可就不幹了,這意味着我(找人)遍歷的時候,須要多跑好多路,浪費好多時間,先不說這個,體院館又不行了,大家這麼個擺法,我這小館可放不下,這也就意味着空間複雜度增長了不少。數組
咱們剛纔所圍繞的都是在 "排隊" 的基本前提下的,但咱們能想到的方法並非很理想,那麼咱們索性就不排隊了,是否是能有更好的解決方式呢?微信
一個有效的方法:函數
讓同窗們(元素)本身找位置隨便站,不過你要知道相對於本身下一位同窗的位置,這樣既解決了空間上的問題,又能經過這種兩兩聯繫的方式訪問(遍歷)到整個隊伍(數組),最重要的是,插入和離開同窗,因爲同窗(元素)之間不存在了那種排隊,相鄰的特色,因此也不會說影響到過多的同窗(元素)只須要和你插入位置的先後兩位同窗溝通好就好了,反正別人也不知道大家之間發生了什麼事學習
好了思路是有了,咱們來看一種最多見的鏈表——單鏈表spa
這種鏈表爲何被稱做單鏈表呢?這是由於它只含有一個地址域,這是什麼意思呢?指針
咱們在鏈表中擯棄了順序表中那種一板一眼的排隊方式,可是咱們必須讓兩個應該相鄰的元素之間有必定的相互關係,因此咱們選擇讓每個元素能夠聯繫對應的下一個元素code
而這個時候咱們就須要給每一個元素安排一個額外的位置,來存儲它的後繼元素的存儲地址,這個存儲元素信息的域叫作指針域或地址域,指針域中儲存的信息也叫做指針或者鏈,
咱們用一張圖 看一下他的結構
結構中名詞解釋
頭指針:一個指向第一個節點地址的指針變量
頭指針具備標識單鏈表的做用,因此常常用頭指針表明單鏈表的名字
頭結點:在單鏈表的第一個結點以前附設一個結點,它沒有直接前驅,稱之爲頭結點
可不存信息,也能夠做爲監視哨,或用於存放線性表的長度等附加信息
指針域中存放首元結點的地址
首元結點:存儲第一個元素的節點
咱們來解釋一下:
(1)鏈表若是爲空的狀況下,若是單鏈表沒有頭結點,那麼頭指針就會指向NULL,若是加上頭結點,不管單鏈表是否爲空,頭指針都會指向頭結點,這樣使得空鏈表與非空鏈表處理一致
(2)使首元結點前插入或刪除元素的時候,與後面操做相同,不須要產生額外的判斷分支,使得算法更加簡單
(以插入爲例講解)在帶頭結點的狀況下,在首元結點前插入或者刪除元素仍與在其餘位置的操做相同,只須要將前一個元素(在這裏是頭結點)的指針域指向插入元素,同時將插入元素的指針域指向原來的第二的元素
而無頭結點的狀況因爲,首元結點前沒有元素,只能經過修改head的先後關係,因此致使了 與在別的位置插入或刪除元素的操做不一樣,在實現這兩個功能的時候就須要額外的寫一個判斷語句來判斷插入的位置是否是首元結點以前的位置,增長了分支,代碼不夠簡潔
總結:頭結點的存在使得空鏈表與非空鏈表處理一致,也方便對鏈表首元結點前結點的插入或刪除操做
###線性表的抽象數據類型定義
咱們在給出單鏈表的定義以前咱們仍是須要先引入咱們線性表的抽象數據類型定義
#ifndef _SEQLIST_H_ #define _SEQLIST_H_ #include "List.h" #include<iostream> using namespace std; template<class elemType> //elemType爲單鏈表存儲元素類型 class linkList:public List<elemType> { private: //節點類型定義 struct Node { //節點的數據域 elemType data; //節點的指針域 Node *next; //兩個構造函數 Node(const elemType value, Node *p = NULL) { data = value; next = p; } Node(Node *p = NULL) { next = p; } }; //單鏈表的頭指針 Node *head; //單鏈表的尾指針 Node *tail; //單鏈表的當前長度 int curLength; //返回指向位序爲i的節點的指針 Node *getPostion(int i)const; public: linkList(); ~linkList(); //清空單鏈表,使其成爲空表 void clear(); //帶頭結點的單鏈表,判空 bool empty()const {return head -> next == NULL;} //返回單鏈表的當前實際長度 int size()const {return curLength;} //在位序i處插入值爲value的節點表長增1 void insert(int i, const elemType &value); //刪除位序爲i處的節點,表長減1 int search(const elemType&value)const; //查找值爲value的節點的前驅的位序 int prior(const elemType&value)const; //訪問位序爲i的節點的值,0定位到首元結點 elemType visit(int i)const; //遍歷單鏈表 void traverse()const; //頭插法建立單鏈表 void headCreate(); //尾插法建立單鏈表 void tailCreate(); //逆置單鏈表 void inverse(); };
單鏈表的類型定義
#ifndef _SEQLIST_H_ #define _SEQLIST_H_ #include "List.h" #include<iostream> using namespace std; template<class elemType> //elemType爲單鏈表存儲元素類型 class linkList:public List<elemType> { private: //節點類型定義 struct Node { //節點的數據域 elemType data; //節點的指針域 Node *next; //兩個構造函數 Node(const elemType value, Node *p = NULL) { data = value; next = p; } Node(Node *p = NULL) { next = p; } }; //單鏈表的頭指針 Node *head; //單鏈表的尾指針 Node *tail; //單鏈表的當前長度 int curLength; //返回指向位序爲i的節點的指針 Node *getPostion(int i)const; public: linkList(); ~linkList(); //清空單鏈表,使其成爲空表 void clear(); //帶頭結點的單鏈表,判空 bool empty()const {return head -> next == NULL;} //返回單鏈表的當前實際長度 int size()const {return curLength;} //在位序i處插入值爲value的節點表長增1 void insert(int i, const elemType &value); //刪除位序爲i處的節點,表長減1 int search(const elemType&value)const; //查找值爲value的節點的前驅的位序 int prior(const elemType&value)const; //訪問位序爲i的節點的值,0定位到首元結點 elemType visit(int i)const; //遍歷單鏈表 void traverse()const; //頭插法建立單鏈表 void headCreate(); //尾插法建立單鏈表 void tailCreate(); //逆置單鏈表 void inverse(); };
(一) 單鏈表的初始化-構造函數
單鏈表的初始化就是建立一個帶頭節點的空鏈表,咱們不須要設置其指針域,爲空便可
template<class elemType> linkList<elemType>::linkList() { head = tail = new Node(); curLength=0; }
注意:new 操做符表明申請堆內存空間,上述代碼中應該判斷是否申請成功,爲簡單,默認爲申請成功,實際上若是系統沒有足夠的內存可供使用,那麼在申請內存的時候會報出一個 bad_alloc exception 異常
(二) 析構函數
當單鏈表對象脫離其做用域時,系統自動執行析構函數來釋放單鏈表空間,其實也就是清空單鏈表內容,同時釋放頭結點
template<class elemType> linkList<elemType>::~linkList() { clear(); delete head; }
(三) 清空單鏈表
清空單鏈表的主要思想就是從頭結點開始逐步將後面節點釋放掉,可是咱們又不想輕易的修改頭指針head的指向,因此咱們引入一個工做指針,從頭結點一直移動到表尾,逐步釋放節點
template<class elemType> void linkList<elemType>::clear() { Node *p, *tmp; p - head -> next; while(p != NULL) { tmp = p; p = p -> next(); delete tmp; } head -> next = NULL; tail = head; curLength = 0; }
下節知識分享咱們將會講到,如何求單鏈表的表長以及遍歷單鏈表、插入,刪除節點等方法的實現,記得關注哦~
自學C/C++編程難度很大,不妨和一些志同道合的小夥伴一塊兒學習成長!
C語言C++編程學習交流圈子,【點擊進入】QQ羣:757874045,微信公衆號:C語言編程學習基地
有一些源碼和資料分享,歡迎轉行也學習編程的夥伴,和你們一塊兒交流成長會比本身琢磨更快哦!