資料結構 --- 線性表 順序儲存 鏈式儲存
線性表是平時一直會用到的資料結構,像python裡面的list這種高階資料結構,其實也是對這種底層結構的封裝。
這篇文章寫了整整4天........
線性表的儲存結構主要分兩大類,一類一類來看。
在這之前,先用虛擬碼來形容一下線性表擁有的基本功能
1 :順序儲存結構
聽名字就知道,這是按照順序來排的,簡單來說,就是在用順序儲存結構來建立線性表的時候,他是在記憶體裡面先申請一塊空地
然後,所有的相同型別的資料元素按照順序,放進去,並且,在記憶體中的存放,也是連續的!
也就是打個比方a,b,c三個元素的記憶體地址,如果按照順序儲存結構的話就會是0x00001 ---> 0x00002 ---> 0x00003
另外,順序儲存的時候,由於是先申請記憶體空間,再放入線性表,所以,正常來說,陣列長度(也就是申請的空間大小)是大於等於線性表長度的
其次,陣列內元素的下標,是要比第i個元素的i少一位的,如下圖.
概括一下,線性表的儲存結構可以用以下程式碼來抽象表示
#define MAXSIZE 20 /* 儲存空間初始分配量 */
typedef struct
{
ElemType data[MAXSIZE]; /* 陣列,儲存資料元素 */
int length; /* 線性表當前長度 */
}SqList;
每個資料元素,其實在記憶體中都有他的固定地址的
然後由於順序線性表的都是連續固定的,所以根據每種元素的資料型別的不同,你可以看出後面元素的不同記憶體地址
假設我們存放的是整數int,他每個元素要佔用c個空間,那麼我們用LOC(a)來表示a這個元素的記憶體地址的話
那麼LOC(ai+1)=LOC(ai)+C
那麼LOC(ai)可以根據a1的LOC來推算出他的記憶體地址LOC(ai)=LOC(a1)+(i-1)*c
所以,由於記憶體地址都是連續的,所以你完全可以通過第一個元素就知道其中第i個元素的值,所以,讀取或存放的時間複雜度為O(1)
1 順序儲存線性表的元素獲取
#define OK 1 #define ERROR 0 #define TRUE 1 #define FALSE 0 typedef int Status; /* Status是函式的型別,其值是函式結果狀態程式碼,如OK等 */ /* 初始條件: 順序線性表L已經存在,1≤i≤ListLength(L) */ /* 操作結果: 用e返回L 中第i個數據元素的值 */ Status GetElem(SqList L,int i,ElemType *e) /* 這裡等於是定義GetElem返回時整型 ,這句函式其實等於int GetElem */ { if(L.length==0 || i<1 || i>L.length) return ERROR; *e=L.data[i-1]; return OK; }
如果陣列長度為0 或 i小於1 或 i 大於陣列長度最大位的後一位,直接返回ERROR
否則,代表能讀到這個數,則給 *e 賦值,值為陣列資料的第i-1下標位的數值,並返回OK
2 順序儲存線性表的元素插入
這個原理和插隊是一樣的,比如一共10個人的隊伍,你認識第4個人,你讓他給你插下隊,那你認識的人就變成第5個了,你變成了第4個,佇列長度要加1
/* 初始條件:順序線性表L已存在,1≤i≤ListLength(L), */
/* 操作結果:在L中第i個位置之前插入新的資料元素e,L的長度加1 */
Status ListInsert(SqList *L,int i,ElemType e)
{
int k;
if (L->length==MAXSIZE) /* 順序線性表已經滿 */
return ERROR;
if (i<1 || i>L->length+1)/* 當i比第一位置小或者比最後一位置後一位置還要大時 */
return ERROR;
if (i<=L->length) /* 若插入資料位置不在表尾 */
{
for(k=L->length-1;k>=i-1;k--) /* 將要插入位置之後的資料元素向後移動一位,k>=i-1等於是說明,當新元素的下標k,越過i-1時 */
L->data[k+1]=L->data[k]; /*每次歷遍都把當前下標位k的元素向後移動一位*/
}
L->data[i-1]=e; /* 將新元素插入 */
L->length++;
return OK;
}
這裡注意,溫習一下,指標訪問結構體成員的方法是L->length,如果是結構體變數訪問成員的話,是 xxx.length
3 順序儲存線性表的元素刪除
/* 初始條件:順序線性表L已存在,1≤i≤ListLength(L) */
/* 操作結果:刪除L的第i個數據元素,並用e返回其值,L的長度減1 */
Status ListDelete(SqList *L,int i,ElemType *e)
{
int k;
if (L->length==0) /* 線性表為空 */
return ERROR;
if (i<1 || i>L->length) /* 刪除位置不正確 */
return ERROR;
*e=L->data[i-1]; /* 用第i個元素給e賦值 */
if (i<L->length) /* 如果刪除不是最後位置 */
{
for(k=i;k<L->length;k++)/* 將刪除位置後繼元素前移 */
L->data[k-1]=L->data[k]; /* 通過元素下標前移元素 */
}
L->length--;
return OK;
}
通過插入元素和刪除元素,我們可以看到,需要對元素進行操作的話,最壞情況下,是每個元素都要移動一下。
所以這時候的時間複雜度是O(n)
至此可以看出順序儲存結構線性表的優缺點了
2.鏈式儲存結構線性表
鏈式儲存結構和順序結構正好相反,他的元素存放,不是排排坐吃果果般連續的,而是分散的,說不定是記憶體地址0x00001------>0x00013------>0x00022
但是,前一個元素,會知道後一個元素的記憶體地址,他是如何知道的呢
這是因為,鏈式儲存結構的元素,不光光有資料值,他還有指標域,指向後一個元素
這樣,我們把資料域+指標域稱為結點Node
每個結點只包含一個指標域的,叫做單鏈表
作為單鏈表來說,總得有一個指向第一個元素結點的東東,這裡,叫做頭指標,單鏈表裡面必須有的東西。
頭指標只有指標域,他指向的是第一個結點的地址.
而相對的,最後一個元素結點的指標域,指向的結果是NULL
另外,有時我們還會在頭指標和第一個元素結點的中間,新增一個頭結點,這個結點的資料域可以不存放東西,也可以存放如陣列長度啊這些的資訊,他的指標域指向第一個元素結點。
然後來看下頭指標和頭結點的區別
這樣,總結一下儲存示意圖
其實對於連結串列來說,他都是由一個一個結點組成的
所以,我們可以用結構指標來表示連結串列
typedef struct Node
{
ElemType data; /* 資料域 */
struct Node *next; /* 指標域 */
}Node; /* 取個別名Node,方便操作 */
typedef struct Node *LinkList; /* 定義LinkList,指向結構體Node的指標 */
假設我們有個指標p,指向連結串列的第i個結點,那麼p->data就表示資料域,p->next就表示指標域
p->next指向的是下一個結點,如果要指向下一個結點的資料域,則是表示成p->next->data
1 單鏈表的讀取
對於單鏈表來說,你要獲取一個元素,你必須從頭開始歷遍
/* 初始條件:順序線性表L已存在,1≤i≤ListLength(L) */
/* 操作結果:用e返回L中第i個數據元素的值 */
Status GetElem(LinkList L,int i,ElemType *e)
{
int j;
LinkList p; /* 宣告一指標p */
p = L->next; /* 讓p指向連結串列L的第一個結點 */
j = 1; /* j為計數器 */
while (p && j<i) /* p不為空或者計數器j還沒有等於i時,迴圈繼續 */
{
p = p->next; /* 讓p指向下一個結點 */
++j;
}
if ( !p || j>i )
return ERROR; /* 第i個元素不存在 */
*e = p->data; /* 取第i個元素的資料 */
return OK;
}
這裡需要注意一下,上一個章節裡面講順序儲存結構線性表的時候,SqList是一個結構體別名, 他定義生成的物件是例項變數
而這個章節的LinkList,是一個指向結構體Node的指標!他定義生成的物件是一個指標!
還有p=L->next不是將L的下一個結點賦值給p,而是將p指向L的下一個結點,p還是指標型別!
上面這個獲取元素的程式碼,他在 j 未達到 i 的過程中,迴圈將p結點向後移動,他將在 j=i 的時候, 將當前p的data,也就是儲存的數值,賦值給 *e
最後return Ok
而通過連結串列的結構我們可以知道,只有上一個結點才知道下一個結點在哪裡,所以,當你需要知道一個結點的值的時候,只能從開頭一個一個歷遍
所以,他的時間複雜度是O(n)
2 單鏈表的插入
事物總有兩面性,連結串列的讀取的時間複雜度是O(n) ,那肯定也有優點,來看看單鏈表的插入
簡單來說,連結串列的插入就是,把新結點的後繼指標指向原來這個位置的結點,再把前一個結點的後繼指標指向新結點
先後次序可以概括為 s->next = p->next ,然後 p->next = s
這裡得記住,千萬不能顛倒次序,如果你先將p->next=s進行了賦值,那後面s->next就等於他s自己了,等於斷鏈了。
/* 初始條件:順序線性表L已存在,1≤i≤ListLength(L), */
/* 操作結果:在L中第i個位置之前插入新的資料元素e,L的長度加1 */
Status ListInsert(LinkList *L,int i,ElemType e)
{
int j;
LinkList p,s;
p = *L; /* p還是指標,指向第一個結點 */
j = 1;
while (p && j < i) /* 尋找第i個結點 */
{
p = p->next; /* 這句很重要,他並不是說p是p的下一個結點,而是說,p從指向當前結點,變成指向下一結點 */
++j;
}
if (!p || j > i)
return ERROR; /* 第i個元素不存在 */
s = (LinkList)malloc(sizeof(Node)); /* 生成新結點(C語言標準函式) */
s->data = e;
s->next = p->next; /* 將p的後繼結點賦值給s的後繼 */
p->next = s; /* 將s賦值給p的後繼 */
return OK;
}
這段程式碼再附加解釋一下
LinkList p,s是定義了2個結構體指標p和s
並通過p=*L的語句,讓p指第一個結點, 具體意思是,傳入的是頭指標本身的記憶體地址,然後再用 * 解引用,解出來的就是頭指標的記憶體地址
注意一下,這裡的LinkList *L ,涉及到了C語言裡,函式引數傳遞的規則.
*L表示地址傳遞,雖然在C裡面沒有真正意義上的地址傳遞。
所以這個過程應該是,傳入L實參的地址,即傳入形參*L的值為 &L (是指標L的本身記憶體地址),也就是要操作指標L的地址的話,需要用一個二級指標來做 * 操作
另外,我理解,這個L其實是一個頭指標,他的指標域是指向第一個結點的
這部分會比較難理解,自己畫了個圖
從上面的結構圖可以看出,p=*L 就等於指標L的地址,在第一輪歷遍的時候,頭指標和p是重合的,但是在後面的歷遍中,p會不斷地向後移動
為什麼不用值傳遞!因為,值傳遞不改變實際引數,而地址傳遞是在記憶體地址上進行操作的,所以改變實際引數的!
而這裡插入元素,是需要改變原來L連結串列的結構的!!
當開始迴圈的第一遍時,p 就指向第一個結點,並且在p && j<i 的情況下,p=p->next,也就是p指標的指向,從當前位置後移一位
3 單鏈表的元素刪除
刪除和插入沒啥大的結構上區別,也即是把前驅指標和後繼指標進行調整,注意順序
/* 初始條件:順序線性表L已存在,1≤i≤ListLength(L) */
/* 操作結果:刪除L的第i個數據元素,並用e返回其值,L的長度減1 */
Status ListDelete(LinkList *L,int i,ElemType *e)
{
int j;
LinkList p,q;
p = *L;
j = 1;
while (p->next && j < i) /* 遍歷尋找第i個元素 */
{
p = p->next;
++j;
}
if (!(p->next) || j > i)
return ERROR; /* 第i個元素不存在 */
q = p->next;
p->next = q->next; /* 將q的後繼賦值給p的後繼 */
*e = q->data; /* 將q結點中的資料給e */
free(q); /* 讓系統回收此結點,釋放記憶體 */
return OK;
}
4 單鏈表的整表建立
連結串列的建立實際上是一個動態的過程,因為每次多加一個元素,他的記憶體空間才會擴充一次,即用即擴充套件,所以他需要用到malloc
另外新建連結串列也可以有2種方法,頭插法和尾插法
先來看頭插法
/* 隨機產生n個元素的值,建立帶表頭結點的單鏈線性表L(頭插法) */
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; /* 隨機生成100以內的數字 */
p->next = (*L)->next;
(*L)->next = p; /* 插入到表頭 */
}
}
沒啥好多說的,有一句需要著重強調
*L = (LinkList)malloc(sizeof(Node))
這句的意思是什麼?(LinkList)malloc(sizeof(Node)) 返回的是一個指標變數!!!是指向一個結點(也就是頭結點)的指標!!!
而這個等式,讓*L這個指標,被引向了指向頭結點的指標,在此刻等同於頭結點的指標
為什麼說是建立了一個頭結點,是因為(*L)->next = NULL
接著是尾插法,這個屬於一般比較正常的思路,從尾部插入
/* 隨機產生n個元素的值,建立帶表頭結點的單鏈線性表L(尾插法) */
void CreateListTail(LinkList *L, int n)
{
LinkList p,r;
int i;
srand(time(0)); /* 初始化隨機數種子 */
*L = (LinkList)malloc(sizeof(Node)); /* L為整個線性表 */
r=*L; /* r為指向尾部的結點 */
for (i=0; i<n; i++)
{
p = (Node *)malloc(sizeof(Node)); /* 生成新結點 */
p->data = rand()%100+1; /* 隨機生成100以內的數字 */
r->next=p; /* 將表尾終端結點的指標指向新結點 */
r = p; /* 將當前的新結點定義為表尾終端結點 */
}
r->next = NULL; /* 表示當前連結串列結束 */
}
這裡又有一個比較重要的語句 r=*L
這裡 *L 是指向頭結點的指標,但是L是代表整個線性表,他會隨著n的增大而增大
r是指向結點的指標,但是他會在每次歷遍,指向不同的結點
為什麼要這樣區分呢?因為連結串列的長度的計算,是按照從*L這個頭結點開始,經過n次歷遍,直到最後一個結點的next為NULL的時候才停止。
也就是說, *L必須停留在起始位置
而 r 在程式剛開始的時候和*L是重合的。只是在後面的歷遍過程中,逐漸分開了。
r 在歷遍過程中,始終為指向最後一個節點的指標。
兩個語句需要補充理解:
r->next = p 將r 和新的結點p進行連線
r = p ,將當前指向 r 的指標 引向 p ,也就是當前最後一個結點,等於是重新整理了 r
這裡補充一下計算連結串列長度的函式,就可以看到,為什麼*L要停留在頭部
/* 初始條件:順序線性表L已存在。操作結果:返回L中資料元素個數 */
int ListLength(LinkList L)
{
int i=0;
LinkList p=L->next; /* p指向第一個結點 */
while(p)
{
i++;
p=p->next;
}
return i;
}
計算過程從L開始歷遍,每次歷遍計數器加1 ,最後返回的是計數器數字
5 單鏈表的整表刪除
他關鍵也是用到指標的移動和free函式
/* 初始條件:順序線性表L已存在。操作結果:將L重置為空表 */
Status ClearList(LinkList *L)
{
LinkList p,q;
p=(*L)->next; /* p指向第一個結點 */
while(p) /* 沒到表尾 */
{
q=p->next;
free(p);
p=q;
}
(*L)->next=NULL; /* 頭結點指標域為空 */
return OK;
}
從前往後依次free掉結點,組後把頭結點的next設為NULL,即置空!
這樣,兩種儲存結構的線性表,就基本是這樣,下一篇再寫點迴圈連結串列和雙向連結串列.
相關推薦
資料結構 --- 線性表 順序儲存 鏈式儲存
線性表是平時一直會用到的資料結構,像python裡面的list這種高階資料結構,其實也是對這種底層結構的封裝。 這篇文章寫了整整4天........ 線性表的儲存結構主要分兩大類,一類一類來看。 在這之前,先用虛擬碼來形容一下線性表擁有的基本功能 1 :順序儲存
C++資料結構-線性表順序儲存結構設計
線性表的順序儲存結構:指的是用一段地址連續的儲存單元一次儲存線性表中的資料元素,如下: 有了以上概念,我們可以使用一維陣列來實現線性表的順序儲存結構, 儲存空間:T* m_marry 當前長度:int m_length 整個類繼承於之前我們寫的List類,
《大話資料結構5》—— 佇列的鏈式儲存結構 —— C++程式碼實現
目錄 鏈佇列 迴圈佇列和鏈式佇列的比較 鏈佇列 ● 實現佇列的最好的方式就是使用單鏈表來實現,佇列的鏈式儲存結構,其實就是線性表的單鏈表,只不過它只能尾進頭出而已——稱為鏈佇列。 ● 那為了操作方便,頭指標指向頭結點,隊尾指標指向終端節點,即最後一個結點元
資料結構-線性表-順序表
順序表程式碼實現 定義 順序表=陣列+整形長度 包含功能 建立空白順序表 陣列轉順序表 向後新增一個元素 顯示所有元素 刪除指定元素 查詢元素 插入元素 #include <iostream> #defi
資料結構線性表順序結構c語言實現程式碼
#include<stdio.h> #include<stdlib.h> typedef int ElementType; typedef struct LNode * PtrToLNode; struct LNode{ ElementType D
資料結構---線性表(順序棧)
SqStack.h #ifndef __SQSTACK_H__ #define __SQSTACK_H__ //一些庫函式的標頭檔案包含 #include <string.h> #include <ctype.h> #include <malloc.h> #
資料結構學習筆記——堆疊之鏈式儲存結構(c語言實現)
棧的鏈式儲存結構使用單鏈表實現,同線性表一樣,鏈式儲存結構的堆疊在儲存空間的利用上顯得更加靈活,只要硬體允許一般不會出現溢位的情況。但鏈式棧相對於順序棧稍顯麻煩,若所需堆疊空間確定,順序棧顯得方便一些。關於鏈式和順序式的選擇視具體情況而定。 1.棧的鏈式儲存結構
資料結構——線性表——順序表
1. 線性表 線性表是最常用且最簡單的資料結構。簡而言之,一個線性表是n個數據元素的有限序列。至於每一個元素的具體含義,在不同的情況下各不相同,它可以是一個數字或一個符號,也可以是一頁書,甚至是更復雜的資訊。 2. 線性表的特點 存在唯一一個被稱
資料結構-------線性表..順序表..連結串列..順序表和連結串列的區別和聯絡(1)
線性表: 線性表是N個具有相同特性的資料元素的有限序列 .線性表是一種在實際中廣泛使用的資料結構,常見的線性表:順序表..連結串列...棧...佇列...字串... 線性表在邏輯上是線性結構,也就說是連續的一條直線,但是在物
資料結構- - 線性表- -順序表_完善
豐富了前邊的功能,更加完善。 #include <iostream> #include <stdlib.h> #define LIST_INIT_SIZE 100 //線性表儲存空間的初始分配量 #define LISTINCREMENT 10 //線性表儲存空間的分配增
資料結構- -線性表- -順序表
線性表屬於線性結構,是最常用且最簡單的一種資料結構,是有n個數據元素的有限序列,同一線性表中的元素必定具有相同特性。長度可根據需要增長或縮短,即對線性表的資料元素可以進行訪問,還可以進行插入和刪除等。 順序儲存與實現: 把邏輯上相鄰的資料元素儲存在物理也相鄰的儲存單元中。用一組地址連續的儲存
資料結構 一元稀疏多項式 利用鏈式儲存實現儲存一元多項式,並計算兩個一元多項式之和
一、實驗原理 利用鏈式儲存實現儲存一元多項式,並計算兩個一元多項式之和。一元多項式由係數和指數構成。 1、create()儲存係數指數:首先建立一個頭結點headLnode,從headLnode->next開始存入係數和指數,只有係數是0時,才表示這個多項式的結束,否
大話資料結構九:佇列的鏈式儲存結構(鏈佇列)
1. 鏈佇列的特點: 鏈佇列其實就是單鏈表,只不過它是先進先出的單鏈表,為了實現方便,程式中設定了隊頭(front),隊尾(rear)兩個指標。 2. Java使用連結串列實現佇列: //結點類,包含結點的資料和指向下一個節點的引用 public class N
資料結構學習筆記-棧的鏈式儲存(C語言實現)
棧是一個特殊的線性表,後進先出,既然是線性表,就會分為順序儲存和鏈式儲存,下面就是棧的鏈式儲存部分,也稱作鏈棧。單鏈表是有頭指標頭節點的,通常鏈棧的棧頂就相當於頭指標,因為初始化的鏈棧是空的,即top=
資料結構學習筆記-佇列的鏈式儲存(C語言實現)
佇列是一種先進先出的線性表。是線性表就會有鏈式儲存,因為先進先出,鏈佇列就是在鏈尾進鏈頭出,這樣一來,定義鏈佇列時就需要定義兩個指標,分別指向佇列的隊頭(相當於頭指標)、隊尾。如果隊頭等於隊尾,則該鏈佇
《資料結構》嚴蔚敏 鏈式儲存實現佇列 演算法3_4_2
這個用連結串列代替陣列來實現佇列 這個程式碼書寫的時候注意ClearQueue(Link_Queue *q) 和DestroyQueue(Link_Queue *q)這兩個函式 前者在寫的時候:要注意第一步是先將rear指標挪到指向佇列首元素的位置,然後一舉將剩下的元素一併釋放,接著再釋
資料結構|二叉樹的鏈式儲存(實驗6.2)
一、實驗目的 1、 熟練理解樹和二叉樹的相關概念,掌握的儲存結構和相關操作實現; 2、 掌握樹的順序結構的實現; 3、 學會運用樹的知識解決實際問題 二、 實驗內容 1、自己確定一個二叉樹(樹結點型別、數目和結構自定)利用鏈式儲存結構方法儲存。實現樹
常用資料結構-二叉樹的鏈式儲存、建立和遍歷
1. 鏈式二叉樹簡介 二叉樹是資料結構——樹的一種,一般定義的樹是指有一個根節點,由此根節點向下分出數個分支結點,以此類推以至產生一堆結點。樹是一個具有代表性的非線性資料結構,所謂的非
資料結構 線性表中,順序儲存和鏈式儲存的優缺點
簡單對順序儲存和鏈式儲存結構做對比: 儲存分配方式; 順序儲存用一段連續的儲存單元一次儲存線性表的資料元素。 鏈式儲存採用鏈式儲存結構,用一組任意的儲存單元存放線性表的元素。時間複雜度衡量;
資料結構線性表之鏈式儲存結構單鏈表(C++)
一. 標頭檔案—linkedlist.h 1 #ifndef _LIKEDLIST_H_ 2 #define _LIKEDLIST_H_ 3 4 #include <iostream> 5 6 template <class T> 7 struc