链表的分类
分類:
單鏈表
雙鏈表:每一個節點有兩個指針域
循環鏈表:能通過任何一個節點找到其他所有的結點
非循環鏈表
?
鏈表中第一個結點的存儲位置叫做頭指針,那么整個鏈表的存取就必須是從頭指針開始進行了。之后的每一個結點,其實就是上一個的后繼指針指向的位置。
這里有個地方要注意,就是對頭指針概念的理解,這個很重要。“鏈表中第一個結點的存儲位置叫做頭指針”,如果鏈表有頭結點,那么頭指針就是指向頭結點數據域的指針。畫一個圖吧。
頭指針就是鏈表的名字。頭指針僅僅是個指針而已。
- 頭結點是為了操作的統一與方便而設立的,放在第一個元素結點之前,其數據域一般無意義(當然有些情況下也可存放鏈表的長度、用做監視哨等等)。
- 有了頭結點后,對在第一個元素結點前插入結點和刪除第一個結點,其操作與對其它結點的操作統一了。
- 首元結點也就是第一個元素的結點,它是頭結點后邊的第一個結點。
- 頭結點不是鏈表所必需的。
- ?
- 是的,對于頭指針,我們也可以有相應的理解了。
- 在線性表的鏈式存儲結構中,頭指針是指鏈表指向第一個結點的指針,若鏈表有頭結點,則頭指針就是指向鏈表頭結點的指針。
- 頭指針具有標識作用,故常用頭指針冠以鏈表的名字。
- 無論鏈表是否為空,頭指針均不為空。頭指針是鏈表的必要元素。
-
?
單鏈表也可以沒有頭結點。如果沒有頭結點的話,那么單鏈表就會變成這樣:
單鏈表源碼:
typdef struct LNode{int data,LinkList *next;}LNode,*LinkList;LinkList head=new LNode(); head->next=null;//插入n個元素LinkList p=head; for(int i=0;i<n;i++){LinkList q=new LNode();q->data=i; q->next=null;p->next=q;}單鏈表帶頭結點&不帶頭結點?
Node *head; //聲明頭結點帶頭結點初始化 void InitList(Node **head){*head=(Node *)malloc( sizeof(Node));(*head)->next=NULL; }帶頭結點尾插入,統一操作 方式一: void CreatList(Node **head){Node *r=*head,*s;int a;while(scanf("%d",&a)){if(a!=0){s=(Node *)malloc(sizeof(Node));s->value=a;r->next=s;r=s; }else{ r->next=NULL;break; }} } 調用CreatList(&head);方式二: void CreatList(Node *head){Node *r=head,*s;... //下面的都一樣 } 調用CreatList(head);------------------------------------------------------------------------------------------------------不帶頭結點初始化 方式一: void InitList(Node **head){*head=NULL; } 調用InitList(&head);方式二: void InitList(Node *head){head=NULL; } 調用InitList(head);不帶頭結點尾插入,第一個節點與其他節點分開操作 void CreatList(Node **head){Node *p,*t; /*p工作指針,t臨時指針*/int a,i=1;while(scanf("%d",&a)){if(a!=0){t=(Node *)malloc(sizeof(Node));t->value=a;if(i==1){*head=t; }else{p->next=t;}p=t;}else{ p->next=NULL;break; }i++;} } 調用CreatList(&head);一、兩者區別:
? ? ?1、不帶頭結點的單鏈表對于第一個節點的操作與其他節點不一樣,需要特殊處理,這增加了程序的復雜性和出現bug的機會,因此,通常在單鏈表的開始結點之前附設一個頭結點。
? ? ?2、帶頭結點的單鏈表,初始時一定返回的是指向頭結點的地址,所以一定要用二維指針,否則將導致內存訪問失敗或異常。
? ? ?3、帶頭結點與不帶頭結點初始化、插入、刪除、輸出操作都不樣,在遍歷輸出鏈表數據時,帶頭結點的判斷條件是while(head->next!=NULL),
?而不帶頭結點是while(head!=NULL),雖然頭指針可以在初始時設定,但是如1所述,對于特殊情況如只有一個節點會出現問題。
? ? ? ? ?
二、為什么不帶頭結點初始化有2種方式,而帶頭結點只有1種方式呢?
?
?????因為不帶頭結點聲明Node *head 時;C編譯器將其自動初始化為NULL,于是根本不需要調用InitList(head);也即不帶頭結點的初始化是個偽操作。而帶頭結點的初始化在堆開辟了一段內存,需要修改head指針變量指向的地址(即head的值),所以要修改head的值,必須傳保存head變量的地址(即二維指針)。而直接調用CreatList(head);相當于傳head變量的值,函數修改的是head的副本,無法真正改變head的值。?
?
?????注:這里可以將head指針看成一個變量(不管它保存的是地址),就比較好理解了。
? ? ??
三(key)、其實本質上還是傳值,傳址的問題,只不過指針本身保存的地址,讓這個過程變得有點糾結。在函數調用需要修改指針變量的指向(value)時,
?應該傳遞指針變量的地址(address)。
? ? ? 另外,對于函數的形參是指針時,只要該參數不在左邊(即都是右值操作),二維指針(形參)就可以簡化為一維指針。如上面帶頭結點的尾插
簡化版本。
總結
- 上一篇: JavaWeb三大组件(ServletF
- 下一篇: Android init.rc分析