------------> 切入主題 :
線性表是數據結構課程內最簡單最基本的一種結構,其存儲方式包括順序存儲和鏈式存儲,實現的基本操作包括:創建,插入,查找,輸出,長度,刪除數據,刪除重複數據,判空,判滿,合併,清空,銷燬。
1.線性表的順序存儲(畫個草圖哈!)
線性表順序存儲的結構體:
typedef int ElementType;
typedef struct{
ElementType *array; //存放數據的指針
int length; //已有數據個數
int capacity; //容量
}SeqList;
這就是一個簡簡單單的array數組數據,其中array是該順序表存儲數據的數組,下標是從0開始到capacity-1,其容量(最多可存儲數據的個數)爲capacity,
我們來觀看上面的圖片,不難發現,該線性表的capacity爲5,length爲3.
現在我們就基於此來實現一系列線性表順序存儲的基本操作。
1.創建操作函數:
該函數創建一個空的線性表,並返回其表指針!
//1.創建
SeqList *createList(int capacity)
{
SeqList *L=(SeqList *)malloc(sizeof(SeqList));//動態申請結構體空間
if(L==NULL)
return NULL //如果動態申請失敗,返回空指針
L->length=0; //初始線性表內無數據
L->capacity=capacity; //給你創建的線性表賦予一定量的容量
L->array=(ElementType *)malloc(capacity*sizeof(ElementType));
//給線性表數組開闢容量爲capacity,ElementType類型的數組,數組存儲的
//數據類型爲ElementType ,這裏給int賦予了一個別名,叫ElementType,
//所以ElementType就是int
return L; //返回你創建的順序表指針
}
2.判空函數:
判斷該線性表是否爲空
int isEmpty(SeqList *L)
{
if(L->length==0)
return 1;
return 0;
}
3.輸出函數:
該函數按順序表中的順序輸出順序表中所有的元素, 每個數後面一個空格,最後一個換行符。若無數據,則什麼也不做
void printList(SeqList *L)
{
if(L->length>0)
{
for(int i=0;i<L->length;i++)
cout<<L->array[i]<<" ";
cout<<endl;
}
}
4.長度函數:
其功能爲返回順序表長度(元素個數)。
int getLength(SeqList *L)
{
return L->length;
}
5.插入函數:
在順序表中第i個位置插入數據x,如果i不在範圍內或者順序表已滿,則什麼都不做。
順序表中第一個元素的位置爲1,其所在的數組下標爲0.
插入數據成功,返回1;否則返回0.
int insertList(SeqList *L,int i,ElementType x)
{
i--;
if(i<0||i>L->length||L->length==L->capacity) return 0;
else
{
for(int k=L->length-1;k>=i;k--)
L->array[k+1]=L->array[k];
L->length++;
L->array[i]=x;
return 1;
}
}
6.查找函數:
若在線性表L中找到與x相等的元素,則返回該元素在線性表中的位置;否則,返回-1。
順序表中第一個元素稱爲第1個元素,其位置爲1,其所在的數組下標爲0.
如果有多個相等的元素,則返回位置最靠前的那個元素的位置。
int find(SeqList *L, ElementType x)
{
for(int i=0;i<L->length;i++)
if(L->array[i]==x)
return i+1;
return -1;
}
7.獲取數據函數:
獲得順序表中第i個元素的數據(保存在p指向的指針中)。
如果獲取成功,返回1,否則返回0。
順序表中第一個元素稱爲第1個元素,其位置爲1,其所在的數組下標爲0.
int getElement(SeqList *L,int i,ElementType *p)
{
i--;
if(i<0||i>=L->length) return 0;
*p=L->array[i];
return 1;
}
8.刪除(非重複)數據函數:
刪除順序表中第i個元素的數據(刪除的數據保存在p指向的指針中)。
如果刪除成功,返回1,否則返回0。
順序表中第一個元素稱爲第1個元素,其位置爲1,其所在的數組下標爲0.
int delElement(SeqList *L, int i, ElementType *p)
{
i--;
if(i<0||i>=L->length) return 0;
*p=L->array[i];
for(int k=i;k<L->length-1;k++)
L->array[k]=L->array[k+1];
L->length--;
return 1;
}
9.刪除(重複)數據函數:
刪除順序表中的重複數據,數據表中有數據重複時, 保留最前面的數據,刪除後面的重複數據。
void delRepeatElement(SeqList *L)
{
int i=0,j=1,leng=1;
while(j<L->length)
{
for(i=0;i<leng;++i)
{
if(L->array[i]==L->array[j])
break;
}
if(i==leng)
L->array[leng++]=L->array[j++];
else
j++;
}
L->length=leng;
}
( 這道題還有一種方法,那就是每當要刪除一個數據時遍歷這個數據前面所有的數,如果有重複的,就不放入數組中,如果沒有,就放入,這也能起到刪除重複數據的效果,大家可以試試!)
10.清除函數:
!!注意:
---->好多人有時候分不清清空操作和銷燬操作的區別:清空只是將你保存的數據清空,但是你的結構並不能發生改變,比如上面那張圖裏面有三個數據,,清空您只需要將該數組的數據個數(長度)置於0,就好了,這樣你遍歷數組也不會取到數據,但有些人會問,爲什麼這樣子就可以呢,我們知道,計算機爲每個類型申請的空間地址是隨機可變的,也就是說,你這個地址有數據,如果你要申請新的一塊空間,一般來說是不會取你那些已經存儲了數據的空間地址,所以這裏不會出現所謂的段錯誤;而銷燬,不僅僅是將數據清空,而且還要將你動態申請的一系列空間全部回收給系統(別浪費了,嘿嘿)
所以這兩者有本質的區別!
清除順序表中的所有數據(請注意數據表數據空間仍然保留)。
void clearList(SeqList *L)
{
L->length=0;
}
11.有序合併函數:
順序表LA和LB中的數據均有序(從小到大),將順序表LA和LB有序(從小到大)合併到LC中,
已知初始LC爲空表(空間已經分配好,並且空間足夠大,即你不用擔心空間不夠) 。(有序!!!)
void mergeList(SeqList *LA, SeqList *LB, SeqList *LC)
{
int k=0;
for(int i=0;i<LA->length;i++)
LC->array[k++]=LA->array[i];
for(int i=0;i<LB->length;i++)
LC->array[k++]=LB->array[i];
LC->length=k;
sort(LC->array,LC->array+k);
}
12.銷燬函數:
銷燬順序表,釋放順序表數據空間以及順序表空間。
void destroyList(SeqList *L)
{
//我們開闢了兩次空間,一次是爲線性表結構體,還有爲順序表內的數組開闢
//了空間
free(L->array); //銷燬這個一維數組空間
free(L); //最後銷燬這個線性表結構體
//這兩個順序不能反,一定是先銷燬線性表內部成員,然後再銷燬這個線性表
}
那麼現在高興地完成了學習線性表順序存儲的操作,相信大家都有了一些對線性表的理解,現在我們來說明它的另一種存儲方式——鏈式存儲
2.順序表的鏈式存儲(帶頭結點)
將鏈式存儲結構體的代碼放出來:
typedef int ElementType;
typedef struct Node{
ElementType data;
struct Node *next;
}Node, *LinkList;
來簡單介紹一下鏈式存儲結構體內容:
1. 鏈式存儲結構體由兩部分組成,data是指該鏈內結點的數據域(就是放數 據的地方嘛),next是一個指針,指向下一個線性表結構體,
2.頭結點: 頭結點是一類特殊的結點,它沒有數據域,只有一個指針域,指向下一個該數據結構類型的結構體
3. 瞄準這個結構體,Node就是這個結構體的別名,是結構體類型,而LinkList是一個指向該結構體類型的指針,下面我們來實現一些操作,
以下出現的 L 均爲該鏈表的頭結點
1.創建函數:
該函數創建一個帶頭節點的空的單鏈表。
LinkList createList()
{
LinkList L = (LinkList)malloc(sizeof(Node));
L->next = NULL; //初始將鏈表結點的指針域置空
return L;
}
2.輸出函數:
該函數按序輸出帶鏈表L中的數據,每個數後面一個空格,最後有一個換行符,其中L是帶頭節點的鏈表。
void printList(LinkList L)
{
Node *p=NULL;
p=L->next; //p是頭結點相連的下一個結點
if(p!=NULL)
{
while(p!=NULL)
{
cout<<p->data<<" "; //輸出此時p結點數據
p=p->next; //不斷遍歷鏈表結點
}
cout<<endl;
}
}
3.長度函數:
該函數返回鏈表長度,即鏈表L中數據結點的數目。
int getLength(LinkList L)
{
int count = 0;
Node *p=L->next;
while(p!=NULL)
{
p=p->next;
count++;
}
return count;
}
4.插入函數(頭插法):
講解一下頭插法:
起初的原鏈表是這樣子的,現在我們要採取頭插法的方式給它再插入一個結點,但我們知道,頭插法插入的新結點一定是頭結點的next,同時插入新的結點後,鏈表的連續性一定不能發生改變!
觀察上面的圖,在插入之前,L的next是q結點,但現在頭插法插入一個結點p,此時要改變L內部next指針的指向,根據頭插法的原理,新插入的結點要在頭結點之後,所以L的next就是p,而p後面的結點就是原來在頭結點L後面的結點q,所以p的next就是q,而此時L的next是q這條路徑被銷燬了,順利成功完成了頭插法操作,同時保持了鏈表的連續性。
該函數在鏈表L中用 頭插法 插入數據X,其中L是帶頭節點的鏈表。
void insertHead(LinkList L, ElementType x)
{
LinkList s;
s=(LinkList)malloc(sizeof(Node));//爲要插入數據申請一個結點空間
s->data=x;
//保持鏈表的連接順序
s->next=L->next;
L->next=s;
}
5.插入函數(尾插法):
結合上面的頭插法,尾插法就是在鏈表的末尾插入一個新的結點
void insertTail(LinkList L, ElementType x)
{
LinkList pre=L;
Node *p=NULL;
p=L->next;
while(p!=NULL) //遍歷找到鏈表的末尾位置
{
pre=p;
p=p->next;
}
//將新結點插入
LinkList s;
s=(LinkList)malloc(sizeof(Node));
s->data=x;
s->next=pre->next;
pre->next=s;
}
6.插入函數(任意位置):
經過上面兩種特殊的插入方法,我就不重複囉嗦了,大致原理就是找到你要插入的位置然後開始插入,保存鏈表的順序性就可以
在帶頭結點的單鏈表L中第i個位置插入值爲x的結點。插入成功返回1,插入不成功返回0.
int insertList(LinkList L, int i, ElementType x)
{
LinkList new_list;
LinkList p=L;
int k=0;
while(p!=NULL&&k<i-1)
{
k++;
p=p->next;
}
if(p==NULL||i<1) return 0; //插入的位置i必須合法,而且該鏈表不爲空。
new_list = (LinkList)malloc(sizeof(Node));
new_list->data = x;
new_list->next=p->next;
p->next=new_list;
return 1;
}
7.查找函數(結點數據法):
該函數在帶頭節點的單鏈表L中查找是否存在結點的數據等於X,如果存在,返回該結點的指針,否則返回NULL。
如果存在多個節點數據等於x,則返回最前面一個數值等於x的節點的指針。
Node* find(LinkList L, ElementType x)
{
Node *p=NULL;
p=L->next;
while(p!=NULL&&p->data!=x)
{
p=p->next;
}
return p;
}
8.查找函數(位置法):
該函數在帶頭節點的單鏈表L中查找第i個節點。如果存在,返回該結點的指針,否則返回空指針。
Node* locate(LinkList L, int i)
{
//某結點位置是該結點的下標+1
i--;
LinkList p=L->next;
int k=0;
if(i<0||L->next==NULL) return NULL;//不合法位置 i
while(p!=NULL && k<i)
{
k++;
p=p->next;
}
if(k==i) return p; //找到了該結點
else return NULL;
}
9.刪除函數(結點數據法):
該函數在帶頭節點的單鏈表L中刪除第一個數值爲x的節點。
如果刪除成功,返回1,否則返回0。
刪除結點,恰恰和插入結點是相反的概念,但保存鏈表連接性的原理還是不能忘記的
int delNode(LinkList L, ElementType x)
{
Node *pre=L;
Node *p=L->next;
while(p!=NULL&&p->data!=x)
{
pre=p;
p=p->next;
}
if(p==NULL) return 0;
else
{
pre->next=p->next;
free(p);
return 1;
}
}
結合代碼再理解一下
int delNode(LinkList L, ElementType x)
{
Node *pre=L;
Node *p=L->next;
while(p!=NULL&&p->data!=x)
{
pre=p;
p=p->next;
}
if(p==NULL) return 0; //該鏈表本身就是空
else
{
pre->next=p->next;//保持鏈表連接性
free(p); //p是要刪除的結點,將其空間釋放
return 1;
}
}
10.刪除函數(位置索引法):
該函數在帶頭節點的單鏈表L中刪除第i個節點,刪除的節點的數據保持在px指向的指針中。 如果刪除成功,返回1,否則返回0。
方法類似上面的刪除
int delNode(LinkList L, int i, ElementType *px)
{
LinkList p=L;
int k=0;
while(p!=NULL && k<i-1)
{
k++;
p=p->next;
}
if(p==NULL || i<1 ||p->next==NULL)
return 0;
Node *mid=p->next;
p->next=mid->next;
*px=mid->data;
free(mid);
return 1;
}
11.清空函數:
我問了傻傻一個逗比的問題:何爲清空?
傻傻答道:將所有的數據都刪除?但數據結構不能發生本質改變
我又呵呵地問一句 :單鏈表又是怎麼實現呢,它的長度好像不會關聯到數據。
傻傻嚥了口唾沫答道:刪除除頭結點後面所有的結點,因爲頭結點不存數據。
我板着臉,最後,哈哈大笑,給了傻傻一腦敲:你說的對,各位,傻傻都學到了,你們呢
傻傻小聲bb嘟嘟一句:我又惹誰了?
我要開啓清空大發了。
這就是清空數據!
該函數刪除單鏈表中的所有數據節點(保留頭節點,清除後爲空單鏈表)。
void clearList(LinkList L)
{
Node* n;
Node* m=L->next;
while(m!=NULL) //從頭結點開始逐個遍歷
{
n=m->next;
free(m);
m=n;
}
L->next=NULL;
}
12.銷燬函數:
銷燬單鏈表就是在清空單鏈表的基礎上把頭結點也銷燬(打掉對方的頭)
該函數銷燬單鏈表。
void destroyList(LinkList L)
{
LinkList c1;
while(L->next!=NULL)
{
c1=L->next;
L->next=c1->next;
free(c1);
}
L->next=NULL;
}
各位,經過這麼一波曲折簡單的學習,腦子是不是基本上掌握順序表的數據結構實現了,下期我會放一些單鏈表應用的東西,有問題評論區給你們劃好了。
製作不易,感謝支持,不喜勿噴!