03.線性表(二)鏈式儲存結構.單鏈表1,鏈式單鏈
鏈式儲存結構.單鏈表11.基本概念 為了表示每個資料元素ai與其直接後繼資料元素ai+1之間的邏輯關係,對資料元素ai來說,除了儲存其本身的資訊之外,還需儲存一個指示其直接後繼的資訊(即直接後繼的儲存位置)(1)資料域:儲存線性表資料元素資料資訊的域稱為資料域;(2)指標域:把儲存直接後繼位置(下一個資料元素的地址)的域稱為指標域,指標域中儲存的資訊為指標或鏈;(3)結點(Node):由資料域和指標域兩部分資訊組成資料元素ai的儲存映像,稱為結點。(4)頭指標:把鏈表中第一個結點的儲存位置叫做頭指標,因此整個鏈表的存取就必須從頭指標開始進行。(5)頭結點:為了更加方便地對鏈表進行操作,我們在單鏈表的第一個結點前附設一個結點,即為頭結點。頭結點的資料域可以不儲存任何資訊,也可以儲存如線性表的長度等附加資訊,頭結點的指標域儲存指向第一個結點的指標。
升華筆記一:頭指標和頭結點的異同?頭指標:a.頭指標是指鏈表指向第一個結點的儲存位置指標,若鏈表有頭結點,則是指向頭結點的指標。b.頭指標具有標識作用,所以常用頭指標冠以鏈表的名字;c.無論鏈表是否為空白,頭指標均不為空白。頭指標是鏈表的必要元素。頭結點:a.頭結點是為了操作的統一和方便而設立的,放在第一個元素的結點之前,其資料域一般無意義(也可以存放鏈表的長度);b.有了頭結點,對在第一元素結點前插入結點和刪除第一結點,其操作與其他結點的操作就同一了;c.頭結點不一定是鏈表必要素。
2.單鏈表 n個結點(ai的儲存映像)鏈結成一個鏈表,即為線性表(a1,a2....an)的鏈式儲存結構,因為此鏈表的每個結點只包含一個指標域,所以稱之為單鏈表。
3.鏈式儲存結構指標程式碼範例/*線性表的單鏈表格儲存體結構 * 結點由存放資料元素的資料域和存放後繼結點地址的指標域組成*/typedef int ElemType;
typedef struct Node{ ElemType data; //int類型成員變數,作為結點的資料域 struct Node *next; //struct Node類型成員指標變數,作為結點的指標域}Node;typedef struct Node *LinkList; //定義LinkList注釋:(1)Node相當於struct Node結構體;*LinkList相當於struct Node結構體(2)假設指標p是指向線性表第i個元素的指標,則結點ai資料域和指標域為p->data、p->next即: p->data的值為該結點ai的資料域,內容是一個資料元素; p->next的值是一個指標,指向第(i+1)個元素(結點)的儲存地址,即指向ai+1的指標即p->data=ai p->next->data=ai+1
4.單鏈表的讀取、插入與刪除操作
(1)單鏈表的讀取操作實現操作: GetElem(LinkList L,int i,ElemType *e),從單鏈表L中取出第i個資料元素,存放到指標變數i指向的地址空間中。演算法思路:不像線性表的順序儲存結構可以很容易的從任意位置讀取一個元素,單鏈表的資料元素讀取必須從頭開始找,即工作指標p後移。a.聲明一個結點p指向鏈表第一個結點,初始化j從1開始遍曆b.當j<i時,就遍曆鏈表,讓p的指標向後移動,不斷指向下一結點,j累加1,直到鏈表末尾p為空白。則說明第i個元素不存在c.如果p為空白結點或者遍曆位置超過i,拋出異常d.尋找成功,返回結點p的資料。源碼實現:/*初始條件:順序線性表L已存在,1=《i<=ListLength(L) *操作結構:用e返回L中第i個資料元素的值*/#define OK 1#define ERROR 0typedef int Status;
typedef int ElemType;
typedef struct Node *LinkList; //定義LinkList
Status GetElem(LinkList L,int i,ElemType *e){ int j; LinkList p; //聲明一個結點p p=p->next; //讓結點p指向鏈表L的第一個結點 j=1; while(p&&j<i) //從鏈表第一個結點開始遍曆,直到j=i-1,讓p指向儲存i-1位置所在的結點 { p=p->next; //讓p指向下一個結點 j++; } if(!p || j>i) return ERROR; *e=p->data; //尋找成功,將鏈表中的第i個元素的資料存放區到指標e指向的空間 return OK;}注釋:p為新定義的一個結點,while(p&&j<i)的作用是使結點p從鏈表第一個結點開始遍曆,直到j=i-1,讓p指向儲存i-1位置所在的結點,直到鏈表末尾p為空白是說明第i個元素不存在。
(2)單鏈表插入操作實現操作: ListInsert(LinkList *L,int i,ElemType e),將資料元素e插入到單鏈表L第i個位置演算法思路: s->next =p->next; p->next=s; a.聲明一個結點p指向鏈表第一個結點,初始化j從1開始遍曆b.當j<i時,就遍曆鏈表,讓p的指標向後移動,不斷指向下一結點,j累加1,直到鏈表末尾p為空白,則說明第i個元素不存在。c.如果p為空白結點或者遍曆位置超過i,拋出異常d.如果尋找成功, -在系統中產生一個空結點s -將資料元素e賦值給s->data; -單鏈表的插入標準語句s->next=p->next,p->next=s源碼實現:/*初始條件:順序線性表L已存在,1=《i<=ListLength(L) *操作結構:將資料元素e插入到單鏈表L第i個位置*/#define OK 1#define ERROR 0typedef int Status;
typedef int ElemType;
typedef struct Node *LinkList; //定義LinkList
Status ListInsert(LinkList *L,int i,ElemType e){ int j; LinkList p,s; //聲明一個結點p p=*L; j=1; while(p&&j<i) //從鏈表第一個結點開始遍曆,直到j=i-1,讓p指向儲存i-1位置所在的結點 { p=p->next; //讓p指向下一個結點 j++; } if(!p || j>i) return ERROR; s=(LinkList)malloc(sizeof(Node)); //產生新結點,即在記憶體中找了一小塊空地,準備用來存放e資料s結點 s->data=e; //關鍵1:尋找成功,將資料元素e賦值給s結點的資料域 s->next =p->next;//關鍵2:將結點p的後繼結點賦值給s的後繼(p->next為指向結點p下一個儲存地址所在的結點) p->next=s; //關鍵3:設定結點p的後繼結點為結點s return OK;}注釋:p為新定義的一個結點,while(p&&j<i)的作用是使結點p從鏈表第一個結點開始遍曆,直到j=i-1,讓p指向儲存i-1位置所在的結點,直到鏈表末尾p為空白是說明第i個元素不存在。
(3)單鏈表刪除操作實現操作: ListDelete(LinkList *L,int i,ElemType *e),刪除單鏈表L中第i個資料元素,並將資料存放至指標e指向的空間中演算法思路:{ p->next=p->next->next(或者q=p->next;p->next=q->next) } a.聲明一個結點p指向鏈表第一個結點,初始化j從1開始遍曆b.當j<i時,就遍曆鏈表,讓p的指標向後移動,不斷指向下一結點,j累加1,直到鏈表末尾p為空白,則說明第i個元素不存在。c.如果p為空白結點或者遍曆位置超過i,拋出異常d.如果尋找成功 -將欲刪除的結點p->next賦值給q -設定結點p的後繼結點為q->next,即p->next=q->next; -將結點q的資料域資料存放至ee.釋放q結點,返回成功標識源碼實現:/*初始條件:順序線性表L已存在,1=《i<=ListLength(L) *操作結構:刪除單鏈表L中第i個資料元素,並將資料存放至指標e指向的空間中*/#define OK 1#define ERROR 0typedef int Status;
typedef int ElemType;
typedef struct Node *LinkList; //定義LinkList
Status ListInsert(LinkList *L,int i,ElemType *e){ int j; LinkList p,q; //聲明一個結點p p=*L; j=1; while(p->next&&j<i) //從鏈表第一個結點開始遍曆,直到j=i-1,讓p指向儲存i-1位置所在的結點 { p=p->next; //讓p指向下一個結點 j++; } if(!p || j>i) return ERROR; q=p->next; //設定q為p的後繼結點 p->next=q->next; //將q的後繼結點設定為p的後繼結點 *e=q->data; //將q結點中的資料給e free(q); //設定完成後,讓系統回收此結點,釋放記憶體 return OK;}
升華筆記二:s、p、s->data、s->next、p->next辨析?(1)LinkList p,s:聲明兩個結點p、s,包含資料域和指標域;(2)s->data:為結點s的資料域,其值為一個資料(3)s->next::為結點s的指標域,其值為下一個結點儲存地址(4)s->next=p->next:將結點p的下一個結點儲存地址賦值給s結點指標域(5)p->next=s; 將結點s設定為p的下一個結點事實上,(4)(5)我們可以這樣理解: 假設初始鏈表相連兩個結點p、q,即......-p-q-......,現在我們需要在結點p、q之間插入一個結點s,則 方法如下:由於p->next=q 則s->next=q //結點s後繼為q p->next=s //結點p後繼為s,此時即可形成......-p-s-q-......
5.順序儲存結構效能分析(1)尋找:最好情況時間複雜度為O(1),最壞情況事件複雜度為O(2)(2)刪除、插入:單鏈表的插入和刪除主要由兩部分組成:第一部分是遍曆查詢第i個元素;第二部分就是插入和刪除元素 從整個演算法來說,我們很容易推匯出:它們的時間複雜度都是O(n)。如果在我們不知道第i個元素的指標位置,單鏈表資料結構在插入和刪除操作上,與線性表的順序儲存解僱是沒有太大的優勢。但是如果我們希望從第i個位置,插入10個元素,對於順序儲存結構意味著,每一次插入都需要移動n-i個元素,每次都是O(n)。而對於單鏈表,我們只需要在第一次找到第i個位置的指標,此時為O(n),接下來只是簡單地通過賦值移動指標而已,時間複雜度都是O(1)。總結:對於插入或刪除資料越頻繁的操作,單鏈表的效率優勢就越明顯。