數據結構(1)線性表/單鏈表的基本實現(c)(詳細的很)

------------> 切入主題
線性表是數據結構課程內最簡單最基本的一種結構,其存儲方式包括順序存儲鏈式存儲,實現的基本操作包括:創建,插入,查找,輸出,長度,刪除數據,刪除重複數據,判空,判滿,合併,清空,銷燬。

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;
}

各位,經過這麼一波曲折簡單的學習,腦子是不是基本上掌握順序表的數據結構實現了,下期我會放一些單鏈表應用的東西,有問題評論區給你們劃好了。

製作不易,感謝支持,不喜勿噴!

傳送門——繼續學習 棧

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