1. 程式人生 > 實用技巧 >線性表知識點總結

線性表知識點總結

線性表的邏輯結構

  • 定義:線性表是具有相同資料型別的n(n≥0)個數據元素的有限序列。其中n為表長。當n=0時 線性表是一個空表
  • 特點:線性表中第一個元素稱為表頭元素;最後一個元素稱為表尾元素。
    除第一個元素外,每個元素有且僅有一個直接前驅。
    除最後一個元素外,每個元素有且僅有一個直接後繼。

線性表的順序儲存結構

  • 線性表的順序儲存又稱為順序表。
    它是用一組地址連續的儲存單元(比如C語言裡面的陣列),依次儲存線性表中的資料元素,從而使得邏
    輯上相鄰的兩個元素在物理位置上也相鄰。

  • 建立順序表的三個屬性:
    1.儲存空間的起始位置(陣列名data)
    2.順序表最大儲存容量(MaxSize)
    3.順序表當前的長度(length)

    #define Maxsize 50  //定義線性表的最大長度 
    typedef int Elemtype  // 假定表中的元素型別是整型 
    typedef struct{
    	Elemtype data [maxsize];  // 順序表中的元素
    	int lengh;  // 順序表的型別定義
    }Sqlist;
    
  • 其實陣列還可以動態分配空間,儲存陣列的空間是在程式執行過程中通過動態儲存分配語句分配

typedef int Elemtype  // 假定表中的元素型別是整型 
typedef struct{
	Elemtype *data;  // 指示動態分配陣列的指標
	int Maxsize,lengh;  // 陣列的最大容量和當前個數
}Sqlist;

//C語言的動態分配語句為
#define InitSize 100
SeqList L;
L.data = (ElemType*)malloc(sizeof(ElemType)*InitSize);
/*
注意:動態分配並不是鏈式儲存,同樣還屬於順序儲存結構,
只是分配的空間大小可以在執行時決定
*/
  • 總結:
    • 1.順序表最主要的特點是隨機訪問(C語言中基於陣列),即通過首地址和元素序號可以在O(1)的時間內找到指定的元素。
    • 2.順序表的儲存密度高,每個結點只儲存資料元素。無需給表中元素花費空間建立它們之間的邏輯關係(因為物理位置相鄰特性決定)
    • 3.順序表邏輯上相鄰的元素物理上也相鄰,所以插入和刪除操作需要移動大量元素。

順序表的操作

  • 1.插入

    • 演算法思路:
      • 1.判斷i的值是否正確
      • 2.判斷表長是否超過陣列長度
      • 3.從後向前到第i個位置,分別將這些元素都向後移動一位
      • 4.將該元素插入位置i 並修改表長
    • 程式碼
    bool ListInsert(SqList &L,int i, ElemType e){  //形參:順序表,插入位置,插入的數
    	if(i<1 || i>L.length +1)
    		return false;
    	if(L.length>=MaxSize)
    		return false;
    	for(int j =L.lenth;j>i;j--)
    		L.data[j] = L.data[j-1];
    	L.data[i-1] = e;
    	L.length++;
    	return true;
    }
    
    • 分析:

      • 最好情況:在表尾插入(即i=n+1),元素後移語句將不執行,時間複雜度為O(1)。
      • 最壞情況:在表頭插入(即i=1),元素後移語句將執行
        n次,時間複雜度為O(n)。
      • 平均情況:假設pi(pi=1/(n+1) )是在第i個位置上插入
        一個結點的概率,則在長度為n的線性表中插入一個結
        點時所需移動結點的平均次數為

  • 2.刪除

    • 演算法思路:
      • 1.判斷i的值是否正確
      • 2.取刪除的元素
      • 3.將被刪元素後面的所有元素都依次向前移動一位
      • 4.修改表長
    • 程式碼
    bool ListDelete(SqList &L,int i, ElemType &e){
    	if(i<1 || i>L.length)
    		return false;
    	e = L.data[i-1]
    	for(int j=i;j<L.length;j++)
    		L.data[j-1] = L.data[j];
    	L.length--;
    	return true;
    }
    
    • 分析

      • 最好情況:刪除表尾元素(即i=n),無須移動元素,時間複雜度為O(1)。
      • 最壞情況:刪除表頭元素(即i=1),需要移動除第一個元素外的所有元素,時間複雜度為O(n)。
      • 平均情況:假設pi(pi=1/n)是刪除第i個位置上結點的概率,則在長度為n的線性表中刪除一個結點時所需移動結點的平均次數為

線性表的鏈式儲存結構

  • 線性表的鏈式儲存是指通過一組任意的儲存單元來儲存線性表中的資料元素。
typedef struct LNode{  // 定義單鏈表節點型別
	Elemtype data;  // 資料域
	struct LNode *next;  // 指標域
}LNode,*LinkList;
// 因為每個節點只有一個指標指向下一個結點,故又稱單鏈表
  • 頭結點和頭指標的區別?
    • 不管帶不帶頭結點,頭指標始終指向連結串列的第一個結點,而頭結點是帶頭結點連結串列中的第一個結點,結點內通常不儲存資訊
  • 為什麼要設定頭結點?
    • 1.處理操作起來方便 例如:對在第一元素結點前插入結點和刪除第一結點起操作與其它結點的操作就統一了
    • 2.無論連結串列是否為空,其頭指標是指向頭結點的非空指標,因此空表和非空表的處理也就統一了。

單鏈表的操作

  • 1.頭插法建立單鏈表:

    • 建立新的結點分配記憶體空間,將新結點插入到當前連結串列的表頭
    • 程式碼

    LinkList CreatList(LinkList &L){
    	LNode *s;  // 輔助指標
    	int x;
    	L = (LinkList)malloc(sizeof(LNode));  // 建立頭結點
    	L->next = Null;  // 建立為空連結串列
    	scanf("%d",&x)
    	while(x!=999){
    		s=(LNode*)malloc(sizeof(LNode));  // 建立新結點
    		s->data = x;
    		s->next = L->next;
    		L->next = s;  // 將新結點插入表中
    		scanf("%d",&x);	 // 讀入下一個結點值
    	}
    	return L;
    }
    
  • 2.尾插法建立單鏈表:

    • 建立新的結點分配記憶體空間,將新結點插入到當前連結串列的表尾
    • 程式碼

    LinkList CreatList(LinkList &L){
    	int x;
    	L = (LinkList)malloc(sizeof(LNode));  // 建立頭結點
    	LNode *s,*r=L;  // 輔助指標,r為表尾指標,指向表尾
    	scanf("%d",&x)
    	while(x!=999){
    		s=(LNode*)malloc(sizeof(LNode));  // 建立新結點
    		s->data = x;
    		r->next = s;
             r=s;  // r指標指向新的表尾結點
    		scanf("%d",&x);	 // 讀入下一個結點值
    	}
    	r->next = Null; //表尾指標指空
    	return L;
    }
    
  • 3.按序號查詢結點

    • 在單鏈表中從第一個結點出發,順指標next域逐個往下搜尋,直到找到第i個結點為止,否則返回最後一個結點指標域NULL。
    • 程式碼
    LNode *GetElem(LinkList l,int i){  //創造結構體指標,一個指標直接遍歷連結串列
    	int j = 1;
    	LNode *p = L->next;
    	if(i==0) return L;
    	if(i<1) return Null;
    	while(p&&j<1){
    		p = p->next;
    		j++;
    	}
    	return p;
    }
    
  • 4.按值查詢結點

    • 從單鏈表第一個結點開始,由前往後依次比較表中各結點資料域的值,若某結點資料域的值等於給定值e,則返回該結點的指標;若整個單鏈表中沒有這樣的結點,則返回NULL。
    • 程式碼
    LNode *LocateElem(LinkList L,ElemType e){  //創造結構體指標,一個指標直接遍歷連結串列
    	LNode *p = L->next;
    	while(p!=Null&&p->data!=e)
    		p = p->next;
    	return p;
    }
    
  • 5.插入

    • 插入操作是將值為x的新結點插入到單鏈表的第i個位置上。先檢查插入位置的合法性,然後找到待插入位置的前驅結點,即第i−1個結點,再在其後插入新結點。

    • 演算法思路:

      1.取指向插入位置的前驅結點的指標
      ① p=GetElem(L,i-1);
      2.令新結點s的指標域指向p的後繼結點
      ② s->next=p->next;
      3.令結點p的指標域指向新插入的結點s
      ③ p->next=s;

  • 6.刪除

    • 刪除操作是將單鏈表的第i個結點刪除。先檢查刪除位置的合法性,然後查詢表中第i−1個結點,即被刪結點的前驅結點,再將其刪除。

    • 演算法思路:

      1.取指向刪除位置的前驅結點的指標 p=GetElem(L,i-1);
      2.取指向刪除位置的指標 q=p->next;
      3.p指向結點的後繼指向被刪除結點的後繼 p->next=q->next
      4.釋放刪除結點 free(q);

雙鏈表

  • 定義
typedef struct LNode{  // 定義單鏈表結點型別
	ElemType data;  // 資料域
	struct LNode *next;  // 指標域
}LNode,*LinkList;

typedef struct DNode{  // 定義雙鏈表結點型別
	ElemType data;  // 資料域
	struct DNode *prior,next;  // 前驅和後繼指標
}DNode,*DinkList;

  • 1.插入:(方法不唯一)

    ① s->next=p->next;
    ② p->next->prior=s;
    ③ s->prior=p;
    ④ p->next=s;

  • 2.刪除:

    ① p->next=q->next;
    ② q->next->prior=p;
    ③ free(q);

迴圈連結串列&&靜態連結串列

  • 迴圈單鏈表:迴圈單鏈表和單鏈表的區別在於,表中最後一個結點的指標不是NULL,而改為指向頭結點,從而整個連結串列形成一個環

  • 迴圈雙鏈表:類比迴圈單鏈表,迴圈雙鏈錶鏈表區別於雙鏈表就是首尾結點構成環

    • 當迴圈雙鏈表為空表時,其頭結點的prior域和next域都等於Head。

  • 靜態連結串列:靜態連結串列是用陣列來描述線性表的鏈式儲存結構。

    定義:

    #define MaxSize 50
    typedef int ElemType
    typedef struct{
    	ElemType data;
    	int next;
    }SLinkList[MaxSize]
    
    • 陣列第一個元素不儲存資料,它的指標域儲存第一個元素所在的陣列下標。連結串列最後一個元素的指標域值為-1。
    • 例子: