【數據結構】—— chapter 02 線性表

2.1 線性表的定義與基本操作

在這裏插入圖片描述

2.1.1 線性表的定義

線性表是具有相同數據類型的n (n≥0) 個數據元素有限序列,其中n爲表長,當n=0時線性表是一個空表。若用L命名線性表,則其一般表示爲
在這裏插入圖片描述
在這裏插入圖片描述

2.1.2 線性表的基本操作

在這裏插入圖片描述
線性表的基本操作:
在這裏插入圖片描述

2.1.3 小結

在這裏插入圖片描述

2.2 線性表的順序表示

在這裏插入圖片描述

2.2.1 順序表的定義

線性表的順序存儲又稱順序表。它是用一種地址連續的存儲單元存儲數據元素,使得邏輯上相鄰的數據元素物理上也相鄰。
(用存儲位置的相鄰來體現數據元素之間的邏輯關係)

在這裏插入圖片描述
注意:線性表中元素的位序是從1開始的,而數組中元素的下標是從0開始的。

1. 靜態分配
在這裏插入圖片描述

#define MaxSize 10  //定義最大長度

typedef struct{
  ElemType data[MaxSize]; //用數組存放數據元素
  int length;   //順序表的當前長度
}SqList;      //順序表的類型定義

//初始化一個順序表
//避免內存中會遺留“髒數據”
void InitList(SqList &L){
     for(int i=0;i<MaxSize;i++)
		 L.data[i] = 0;
	 L.length = 0;
}
int main()
{
   SqList L; //聲明一個順序表
   InitList(L);  //初始化順序表
 
   return 0;
}

2. 動態分配
在這裏插入圖片描述
在這裏插入圖片描述

#include <stdio.h> 
#include <stdlib.h>  //malloc、free函數的頭文件
#define InitSize 10  //表長度的初始定義

typedef struct{
  ElemType *data; //指示動態分配數組的指針
  int MaxSize;    //數組的最大容量
  int length;   //順序表的當前長度
}SqList;      //順序表的類型定義


void InitList(SqList &L){
   L.data = (ElemType *)malloc(sizeof(ElemType)*InitSize);
   L.length = 0;
   L.MaxSize = InitSize;
}

//增加動態數組的長度
void IncreaseSize(SqList &L,int len){
   ElemType *p = L.data;
   L.data = (ElemType *)malloc(sizeof(ElemType)*(MaxSize+len)); //重新分配了15個存儲單元
   for(int i=0;i<L.length;i++)
	   L.data[i] = p[i];   //將數據複製到新區域(時間開銷大)
	L.MaxSize = L.MaxSize+len;
	free(p);
}

int main()
{
   SqList L; //聲明一個順序表
   InitList(L);  //初始化順序表
   //.....隨便插入幾個元素
   IncreaseSize(L,5);
   return 0;
}

3. 順序表的特點
在這裏插入圖片描述

4. 小結
在這裏插入圖片描述

2.2.2 順序表上基本操作的實現

在這裏插入圖片描述
1. 插入
ListInsert(&L,i,e):插入操作。在表L中的 第i個(i是位序)位置上 插入指定元素e。
1 ≤ i ≤ L.length+1。
(插入、刪除代碼建立在順序表的“靜態分配”實現方式之上, “動態分配”也雷同。)

//在位序i插入元素e
bool ListInsert(SqList &L,int i,int e) {
	if (i<1 || i>L.length + 1)  return false;
	if (L.length >= MaxSize)   return false;
	for (int j = L.length; j >= i; j--){
		L.data[j] = L.data[j - 1];
	 }
		L.data[i-1] = e;
		L.length += 1;
	return true;
}

2. 插入操作的時間複雜度分析

問題規模n= L.length (表長)
在這裏插入圖片描述
3. 刪除
ListDelete(&L,i,&e):刪除操作。刪除表L中 第i個(i是位序)位置 的元素,並用e返回刪除元素的值。
1 ≤ i ≤ L.length。

//刪除位序i的元素,並用e帶回
bool ListDelete(SqList& L, int i, int& 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;
}

4. 刪除操作的時間複雜度分析
在這裏插入圖片描述

2.3 線性表的鏈式表示

2.3.1 單鏈表的定義

1. 什麼是單鏈表
在這裏插入圖片描述
2. 單鏈表中結點類型的描述以及單鏈表的表示
單鏈表中結點類型的描述:

typedef struct LNode {
	int data; 
	struct LNode* next;
}LNode,*LinkList;

單鏈表的表示:
通常用頭指針標識一個單鏈表
LNode * L; //聲明一個指向單鏈表第一個結點的指針
或 LinkList L; //聲明一個指向單鏈表第一個結點的指針(代碼可讀性更強)
在這裏插入圖片描述

3.不帶頭結點的單鏈表
L= Null 是空表。
在這裏插入圖片描述

#include <stdio.h>


typedef struct LNode {
	int data; 
	struct LNode* next;
}LNode,*LinkList;

//初始化一個空的單鏈表
bool InitList(LinkList &L) {
	L = NULL;   //防止髒數據
	return true;
}

//判斷單鏈表是否爲空
bool IsEmpty(LinkList L) {
	if (L == NULL)  return true;
	else  return false;

}

void test() {
	LinkList L;  ////聲明一個指向單鏈表的指針
	InitList(L);  //初始化一個空表
	//......後續代碼......

}

4. 帶頭結點的單鏈表
L->next == NULL是空表。
在這裏插入圖片描述

#include <stdio.h>
#include <stdlib.h>  

typedef struct LNode {
	int data; 
	struct LNode* next;
}LNode,*LinkList;

//初始化一個空的單鏈表
bool InitList(LinkList &L) {
	L = (LNode *)malloc(sizeof(LNode)); //分配一個頭結點
	if (L == NULL)  //malloc分配失敗
		return false;
	L->next = NULL; 
	return true;
}

//判斷單鏈表是否爲空
bool IsEmpty(LinkList L) {
	if (L->next == NULL)  return true;
	else  return false;
}

void test() {
	LinkList L;  ////聲明一個指向單鏈表的指針
	InitList(L);  //初始化一個空表
	//......後續代碼......

}

5. 小結
在這裏插入圖片描述

2.3.2 單鏈表上基本操作的實現

在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述

一、單鏈表的插入刪除

1. 按位序插入
在這裏插入圖片描述
插入結點操作將值爲x的新結點插入到單鏈表的第i個位置上。先檢查插入位置的合法性,然後找到待插入位置的前驅結點,即第i-1個結點,在它後面插入結點。

(1) 帶頭結點
頭結點可以看作“第0個”結點(這樣寫代碼便於理解),j 初始設0

在這裏插入圖片描述

//在第i個位置插入元素e
bool ListInsert(LinkList &L,int i,int e) {
	if (i < 1)   //i值不合法
		return false;
		
	int j = 0;  //當前p指向的結點位置
	LNode* p;   //指針p指向當前掃描到的結點
	p = L;

	while (j < i - 1 && p != NULL) {  //循環找到第i-1個結點
		p = p->next;
		j++;
	}

	if (p == NULL)  //i值不合法
		return false;

	LNode* s = (LNode*)malloc(sizeof(LNode));
	s->data = e;
	s->next = p->next;
	p->next = s;

	return true;  //插入成功
}

最好時間複雜度:O(1)
最壞時間複雜度:O(n)
平均時間複雜度:O(n)

(2) 不帶頭結點
不帶頭結點,那麼 j 初始設1

在這裏插入圖片描述

//在第i個位置插入元素e,不帶頭結點
bool ListInsert(LinkList& L, int i, int e) {
	if (i < 1)   //i值不合法
		return false;

	if (i == 1) {  //插入第一個結點的操作與其他結點的操作不同
		LNode* s = (LNode*)malloc(sizeof(LNode));
		s->data = e;
		s->next = L;
		L = s;   //頭指針指向新結點
		return true;
	}


	LNode* p;   //指針p指向當前掃描到的結點
	p = L;
	int j = 1;  //當前p指向的結點位置
	while (j < i - 1 && p != NULL) {  //循環找到第i-1個結點
		p = p->next;
		j++;
	}

	if (p == NULL)  //i值不合法
		return false;

	LNode* s = (LNode*)malloc(sizeof(LNode));
	s->data = e;
	s->next = p->next;
	p->next = s;

	return true;  //插入成功

}

最好時間複雜度:O(1)
最壞時間複雜度:O(n)
平均時間複雜度:O(n)

2. 指定結點的後插操作

//在p結點之後插入元素e
bool InsertNextNode(LNode *p, int e) {
	if (p == NULL)
		return false;
	LNode* s = (LNode*)malloc(sizeof(LNode));
	if (s == NULL)//內存分配失敗
		return false;
	s->data = e;
	s->next = p->next;
	p->next = s;
	return true;
}

時間複雜度:O(1)

3. 指定結點的前插操作
在這裏插入圖片描述
想在指定結點前插的話,有兩種方法;
法一:循環查找p的前驅結點q,再對q後插。時間複雜度是O(n)。

在這裏插入圖片描述

時間複雜度:O(n)

法二:在p結點後插入結點s,然後將p結點的數據域與s結點的數據域交換。

//在p結點之前插入結點s
bool InsertPriorNode(LNode* p, LNode* s) {
	if (p == NULL || s == NULL)
		return false;
	s->next = p->next;
	p->next = s;    //s連到p之後

	int temp = p->data; //交換數據域部分 
	p->data = s->data;
	s->data = temp;
	return true;
}

時間複雜度:O(1)

4. 按位序刪除
在這裏插入圖片描述
(1) 帶頭結點的:

//刪除第i個位置的元素,並用e返回
bool ListDelete(LinkList& L, int i, int& e) {
	if (i < 1) return false;
	LNode* p;   //p指向當前掃描到的結點
	p = L;
	int j = 0;   //當前p指向的是第幾個結點

	while (j < i - 1 && p != NULL) {
		p = p->next;
		j++;
	}

	if (p == NULL)  //i值不合法
		return false;
	if (p->next == NULL) //第i-1個結點之後已無其他結點
		return false;
 
	LNode* q = p->next; //q指向被刪除的結點
	e = q->data;       //用e返回元素的值
	p->next = q->next;
	free(q);      //釋放結點的存儲空間
	return true;  //刪除成功

}

最好時間複雜度:O(1)
最壞時間複雜度:O(n)
平均時間複雜度:O(n)

(2) 不帶頭結點的:

bool ListDelete(LinkList& L, int i, int& e) {
	if (i < 1) return false;
	LNode* p;   //p指向當前掃描到的結點
	p = L;
    int j = 1;   //當前p指向的是第幾個結點
    
	if (i == 1) {
		LNode* s;
		s = L;
		L = s->next;
		free(s);
	}
    
    //.......剩下的與上面 帶頭結點代碼中while開始到結束 的一樣。
}

最好時間複雜度:O(1)
最壞時間複雜度:O(n)
平均時間複雜度:O(n)

5. 指定結點的刪除
在這裏插入圖片描述

刪除結點p,需要修改其前驅結點的next指針。有兩個方法。
法1:傳入頭指針,循環尋找p的前驅結點。

時間複雜度:O(n)

法2:將刪除結點的數據域與後驅結點的數據域交換,然後直接刪除後驅結點就好。
( 類似於指定結點前插的實現)
如果p是最後一個結點,不能 用方法二 ,只能從表頭開始依次尋找p的前驅,使用方法一。
這裏可以發現單鏈表的侷限性:無法逆向檢索,有時候不太方便。

//刪除指定結點p
bool DeleteNode(LNode* p) {
	if (p == NULL)  return false;
	LNode* q = p->next;
	p->data = q->data;
	p->next = q->next;
	free(q);
	return true;
}

時間複雜度:O(1)

6. 小結
在這裏插入圖片描述

二、單鏈表的查找

1. 按位查找
在這裏插入圖片描述
在這裏插入圖片描述

時間複雜度:O(n)

2. 按值查找

//按值查找,找到數據域==e的結點
LNode* LocateElem(LinkList L, int e) {
	LNode* p = L ->next;
	//從第1個結點開始查找數據域爲e的結點
	while (p != NULL && p->data != e)
		p = p->next;
	return p; //找到後返回該結 點指針,否則返回NULL
}

時間複雜度:O(n)

3. 求表的長度
求表長操作就是計算單鏈表中數據結點(不含頭結點)的個數。設置一個計數器len。
(1)帶頭結點的:

//求表的長度
int Length(LinkList L) {
	int len = 0;  //統計表長
	LNode* p = L;
	while (p ->next != NULL) {
		p = p->next;
		len++;
	}
	return len;
}

(2)不帶頭結點的:
只需要把len初始值改爲1即可。

(1) (2) 時間複雜度都是:O(n)

4. 小結
在這裏插入圖片描述

三、單鏈表的建立

如果給你很多個數據元素(ElemType),要把它們存到一個單鏈表裏邊,怎麼搞?
Step 1:初始化一個單鏈表
Step 2:每次取一個數據元素,插入到表尾/表頭
這裏只寫帶頭結點的。
1. 尾插法
在這裏插入圖片描述
在這裏插入圖片描述

//尾插法建立單鏈表
LinkList List_Taillnsert(LinkList& L) {
	int x;
	LNode *s,*r = L;  //r是表尾指針
	L = (LNode*)malloc(sizeof(LNode));  //頭結點

	scanf_s("%d", &x); //輸入結點的值

	while (x != 9999) {
		s = (LNode*)malloc(sizeof(LNode));
		s->data = x;
		r->next = s;
		r = s;   //r指向新的表尾結點;永遠保持r指向最後一個結點
		scanf_s("%d", &x);
	}
	r->next = NULL;  //尾結點指針置空
	return L;
}

時間複雜度: O(n)

2. 頭插法
在這裏插入圖片描述
在這裏插入圖片描述

//頭插法
LinkList Head_Insert(LinkList &L) {
	int x;
	LNode* s; 
	L = (LNode*)malloc(sizeof(LNode));  //頭結點
	L->next = NULL;  //初始爲空鏈表
	
	scanf_s("%d",&x);

	while (x != 9999) {
		s = (LNode*)malloc(sizeof(LNode));
		s->data = x;

		s->next = L->next;
		L->next = s;
		scanf_s("%d",&x);
	}
	return L;
}

時間複雜度: O(n)

3. 小結
在這裏插入圖片描述

2.3.3 雙鏈表

以下代碼都是基於帶頭結點的
單鏈表結點中只有一個指向其後繼的指針,使得單鏈表只能從頭結點依次順序地向後遍歷。要訪問某個結點的前驅結點(插入、刪除操作時),只能從頭開始遍歷,訪問後繼結點的時間複雜度爲O(1),訪問前驅結點的時間複雜度爲O(n)。
爲了克服單鏈表的上述缺點,引入了雙鏈表,雙鏈表結點中有兩個指針 prior和next,分別指向其前驅結點和後驅結點。如下圖:
在這裏插入圖片描述
1. 雙鏈表的初始化

//雙鏈表
typedef struct DNode {
	int data;
	struct DNode* prior;
	struct DNode* next;
}DNode,*DLinkList;

//雙鏈表的初始化
bool InitList(DLinkList &L) {
	L = (DNode*)malloc(sizeof(DNode));  //頭結點
	if (L == NULL)  return false;
	L->prior = NULL;   //頭結點的prior永遠指向NULL
	L->next = NULL;    //頭結點目前後面沒有結點
	return true;
}

//判斷雙鏈表是否爲空
bool Empty(DLinkList L) {
	if (L->next == NULL)  return true;
	else   return false;
}

2. 雙鏈表的插入
如果p是最後一個結點,就要加上一句 if(p->next != NULL) //如果p結點有後繼結點。
注意:1 2步必須在4之前。

//雙鏈表的插入,在p結點之後插入s結點
bool InsertNextDNode(DNode *p,DNode *s) {
	if (p == NULL || s == NULL) return false;
    
	s->next = p->next;   // 1
	if(p->next != NULL)  //如果p結點有後繼結點
	    p->next->prior = s;   // 2
	s->prior = p;       // 3
	p->next = s;        // 4
	return true;

}

3. 雙鏈表的刪除
如果p是最後一個結點,就要加上一句 if(q->next != NULL) //q結點不是最後一個結點

//雙鏈表的刪除,刪除p結點的後繼節點
bool DeleteNextDNode(DNode* p) {
	if (p == NULL)  return false;
	DNode* q = p->next; //要刪除的結點q
	if (q == NULL)  return false; //若p沒有後繼
	p->next = q->next;
	if(q->next != NULL)  //q結點不是最後一個結點
	    q->next->prior = p;
	free(q);    //釋放結點空間
	return true;
}

4. 雙鏈表的遍歷
在這裏插入圖片描述
5. 小結
在這裏插入圖片描述

2.3.4 循環鏈表

在這裏插入圖片描述

1. 循環單鏈表

在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述
在這裏插入圖片描述

循環單鏈表的初始化:

//初始化循環單鏈表
bool InitList(LinkList& L) {
	L = (LNode*)malloc(sizeof(LNode)); //分配一個頭結點
	if (L == NULL)  return false;
	L->next = L;  
	return true;
}

//判斷循環單鏈表是否爲空
bool Empty(LinkList L) {
	if (L->next == L)  return true;
	else  return true;
}

2. 循環雙鏈表
在這裏插入圖片描述在這裏插入圖片描述
循環雙鏈表的初始化:

 //初始化循環雙鏈表
bool InitDLinkList(DLinkList &L) {
	L = (DNode*)malloc(sizeof(DNode)); //分配一個頭結點
	if (L == NULL)  return false;
	L->prior = L;
	L->next = L;
	return true;
}

//判斷循環雙鏈表是否爲空
bool Empty(DLinkList L) {
	if (L->next == L)  return true;
	else  return false;
}

循環雙鏈表的插入:
在這裏插入圖片描述
循環雙鏈表的刪除:
在這裏插入圖片描述
3. 小結
在這裏插入圖片描述

2.3.5 靜態鏈表

在這裏插入圖片描述

1. 什麼是靜態鏈表
其實就是數組,只是每個位置存儲的是結點類型的元素,指針域放的是下一個元素的下標。
在這裏插入圖片描述
2. 如何定義一個靜態鏈表
在這裏插入圖片描述
3. 簡述基本操作的實現
在這裏插入圖片描述在這裏插入圖片描述
4. 小結
在這裏插入圖片描述

2.3.6 順序表和鏈表的比較

在這裏插入圖片描述
1. 邏輯結構
在這裏插入圖片描述
2. 物理結構
在這裏插入圖片描述
3. 基本操作
在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述
4. 順序表與鏈表的適用場景
在這裏插入圖片描述
5. 知識回顧&&重要考點
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章