[數據結構] 線性表---鏈式存儲結構

寫在前面,本章我們可以瞭解到:
1.線性表的鏈式存儲結構定義
2.線性錶鏈式存儲結構代碼描述

typedef struct Node
{
	ElemType data;
	struct Node *next;
}Node;
typedef struct Node *LinkList;

3.對單鏈表的整表創建、整表刪除、插入、刪除、讀取
4.比較鏈式存儲結構和線性存儲結構


1.線性表的鏈式存儲結構

鏈式存儲結構的特點

  1. 用一組任意的存儲單元存儲線性表的數據元素,這組存儲單元可以是連續的,也可以是不連續的,這些數據元素可以存在內存未被佔用的任意位置
  2. 鏈式結構中,除了要存數據元素外,還要存儲它的後繼元素的存儲地址

爲了表示每個數據元素 ai 與其後繼元素 ai+1 之間的邏輯關係,數據元素 ai 來說,除了存儲其本身的信息之外,還需要存儲一個指示其直接後繼的信息。

我們把存儲數據元素信息的域稱爲數據域,把存儲直接後繼位置的域稱爲指針域。這兩部分信息組成數據元素 ai 的存儲映像,稱爲結點(Node)

n個結點鏈結成一個鏈表,就是線性表的鏈式存儲結構,每個結點中只包含一個指針域,所以叫單鏈表,如下圖
在這裏插入圖片描述

鏈表中第一個結點的存儲位置叫做頭指針,整個鏈表的存取就必須是從頭指針開始,那麼最後一個結點指針爲“空”(NULL),如下圖
在這裏插入圖片描述

爲了和其他結點一樣對鏈表進行操作,會在單鏈表的第一個結點前加一個結點,稱爲頭結點,頭結點的數據域可以不存儲任何信息,也可以存儲線性表的長度等附加信息,如下圖
在這裏插入圖片描述


2.線性錶鏈式存儲結構代碼描述

單鏈表中,我們在C語言中可用結構指針來描述,結點由存放數據元素的數據域存放後繼結點地址的指針域組成。

typedef struct Node
{
ElemType data;
struct Node *next;
}Node;
typedef struct Node *LinkList;

我們假設 p 是指向線性表第 i 個元素的指針,則該結點 ai 的數據域,我們可以用 p->data 來表示,結點 ai 的指針域可以用 p->next 來表示, p->next 指向第 i + 1個元素, p->next->data 是 第i+1個結點的值,如下圖所示:
在這裏插入圖片描述


3.單鏈表的操作

① 單鏈表的整表創建

對比順序存儲結構的創建,順序存儲結構的創建其實就是一個數組的初始化,單鏈表是一種動態結構,所佔用空間的大小和位置是不需要預先分配劃定的,可以根據系統的情況和實際需求即時生成,所以說,創建單鏈表的過程就是一個動態生成鏈表的過程。
算法思路:

  • 1.聲明一結點p和計數器變量i;
  • 2.初始化一空鏈表L;
  • 3.讓L的頭結點的指針指向NULL,即建立一個帶頭結點的單鏈表
  • 4.循環:
    生成一新結點賦值給 p;
    隨機生成一數字賦值給 p 的數據域 p->data;
    將 p 插入到頭結點與前一新節點之間;(該插入方法有兩種,頭插法和尾插法)

我們先來介紹頭插法,其實就是插隊的辦法,始終讓新結點在第一的位置

void CreateListHead(LinkList *L, int n)
{
 	LinkList p;
	int i;
	srand (time(0));
	*L = (LinkList)malloc(sizeof(Node));
	(*L) ->next = NULL;
	for ( i = 0; i<n; i++)
	{
		p = (LinkList)malloc(sizeof(Node));
		p->data = rand()%100 +1;
		p->next = (*L)->next;
		(*L)->next = p;
	}
}

尾插法:所謂的先來後到,每次新結點來的時候都插在終端結點的後面,稱之爲尾插法

void CreateListTail(LinkList *L,int n)
{
		LinkList p,r;
		int i;
		srand(time(0));
		*L = (LinkList)malloc(sizeof(Node));
		r = *L;
		for (i = 0; i<n; i++)
		{
			 p = (Node*)malloc(sizeof(Node));
			p->data = rand()%100+1;
			r->next = p;
			r = p;
		}
		r->next = NULL;
}

②單鏈表的整表刪除

算法思路:

  • 1.聲明一結點p和q;
  • 2.將第一個結點賦值給p;
  • 3.循環:
    將下一結點賦值爲q;
    釋放p;
    將q賦值給p;
Status ClearList(LinkList *L)
{
	LinkList p,q;
	p = (*L) -> next;
	while(p)
	{
		q = p->next;
		free(p);
		p = q;
	}
	(*L)->next = NULL;
	return OK;
}

③獲取第 i 個元素的數據的操作

思路:

  • 1.聲明一個結點p指向鏈表第一個結點,初始化 j 從 1開始
  • 2.當j < i 時,讓p的指針不斷向後移動,j 累計+1
  • 3.若到鏈表末尾 p 爲空,說明第 i 個元素不存在
  • 4.若查找成功,返回結點p 的數據
Status GetElem (LinkList L, int i, ElemType *e)
{
	int j = 1;
	LinkList p;
	p = L->next;
	while(p && j < i)
	{
		p = p->next;
		++j;
	}
	if (!p || j > j)
		return ERROR;
	*e = p->data;
	return OK;
}

④單鏈表的插入

如下圖所示,假設存儲元素爲 e 的結點 是,要實現結點 p,p->next 和 s 的邏輯關係,將 s 插入到兩個結點之間, 我們只需要 修改 s->next 和 p->next 即可,不需要修改其他元素。。。
在這裏插入圖片描述

讓p的後置結點變成 s 的後置結點,s變成 p的後置結點

思路:

  • 1.聲明一個結點 p 指向鏈表第一個結點, 初始化 j從 1開始
  • 2.當 j < i 時,就遍歷鏈表,讓 p 的指針不斷往後移動,j 累加 1
  • 3.鏈表末尾爲 p 爲空,說明第 i 個元素不存在
  • 4.查找成功 ,生成一個空結點
  • 5.將數據元素 e 賦值爲 s->data
  • 6.單鏈表標準插入 s->next = p->next; p->next = s;
  • 7.返回成功
Status ListInsert(LinkList *L, int i, ElemType e)
{
	int j;
	LinkList p,s;
	p = *L;
	j = 1;
	while(p && j < i)
	{
		p = p->next;
		++j;
	}
	if (!p || j > i)
		return ERROR;
	s = (LinkList)malloc(sizeof(Node));
	s->data = e;
	s->next = p->next;
	p->next = s;
	return OK;
}

⑤單鏈表的刪除

如下圖,將結點q刪除,就是將q的前繼結點的指針繞過q,直接指向q的後繼結點就好
在這裏插入圖片描述
思路:

  • 1.聲明結點p指向第一個結點,初始化 j 從1 開始
  • 2.當 j < i 時,遍歷鏈表, p 的指針向後移動,不斷指向下一個結點, j 累加 1
  • 3.若到鏈表末尾 p 爲空 ,則說明第 i 個元素不存在
  • 4.若查找成功,就要刪除的結點 p->next 賦值給q;
  • 5.單鏈表標準刪除語句 p->next = q->next;
  • 6.將q中的值賦值給e
  • 7.釋放q
  • 8.返回成功
Status ListDelete(LinkList *L, int i , ElemType *e)
{
	int j = 1;
	LinkList p ,q;
	p = *L;
	while(p && j < i)
	{
	p = p->next;
	++j;
	}
	if ( !(p->next) || j > i)
	return ERROR;
	q = p->next;
	*e = q->data;
	p->next = q->next;
	free(q);
	return OK;
}

4.單鏈表結構與順序存儲結構的優缺點

從存儲分配方式上來說
順序存儲結構用一段連續的存儲單元依次存儲線性表的數據元素
單鏈表採用鏈式存儲結構,一組任意的存儲單元存放數據元素

從時間性能上來說
查找操作時,順序存儲結構O(1) 單鏈表 O(n)
插入和刪除,順序存儲結構需要移動部分元素,時間爲O(n),單鏈表爲O(1)

從空間性能上來說
順序存儲結構需要預分配存儲空間,分大了浪費,分小了容易超出
單鏈表不需要分配,只要有空間就可以分配

總結,若線性表需要頻繁查找,很少進行插入和刪除操作時,採用順序存儲結構,頻繁使用插入刪除時,宜採用單鏈表結構

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