數據結構學習筆記
線性表
概念
全名爲線性存儲結構。使用線性表存儲數據的方式可以這樣理解,即“把所有數據用一根線兒串起來,再存儲到物理空間中”
分類
將數據依次存儲在連續的整塊物理空間中,這種存儲結構稱爲順序存儲結構(簡稱順序表)
數據分散的存儲在物理空間中,通過一根線保存着它們之間的邏輯關係,這種存儲結構稱爲鏈式存儲結構(簡稱鏈表)
順序表
概念
順序表對數據的物理存儲結構也有要求。順序表存儲數據時,會提前申請一整塊足夠大小的物理空間,然後將數據依次存儲起來,存儲時做到數據元素之間不留一絲縫隙。如圖
Tips:順序表在申請時需要爲順序表記錄兩個數據。
1:順序表申請的存儲容量
2:順序表的長度,也就是表中存儲數據元素的個數
3:順序表申請的存儲容量要大於順序表的長度
聲明順序表
typedef struct Table{
int* head;//聲明瞭一個名爲head的長度不確定的數組,也叫“動態數組”
int length;//記錄當前順序表的長度
int size; //記錄順序表分配的存儲容量
}table;
Tips:
head 是我們聲明的一個未初始化的動態數組,不要只把它看做是普通的指針
初始化順序表
給鏈表頭 head 動態數據申請足夠大小的物理空間;
給 size 和 length 賦初值;
table initTable(){
table t;
//構造一個空的順序表,動態申請存儲空間
t.head=(int*)malloc(Size*sizeof(int));
if (!t.head) //如果申請失敗,作出提示並直接退出程序
{
printf("初始化失敗");
exit(0);
}
t.length=0;//空表的長度初始化爲0
t.size=Size;//空表的初始存儲空間爲Size
return t;
}
順序表元素賦值
for (int i=1; i<=Size; i++)
{
t.head[i-1]=i;
t.length++;
}
順序表顯示數據
//輸出順序表中元素的函數
void displayTable(table t)
{
for (int i=0;i<t.length;i++)
{
printf("%d ",t.head[i]);
}
printf("\n");
}
順序表插入元素
插入函數,其中,elem爲插入的元素,add爲插入到順序表的位置
table addTable(table t,int elem,int add)
{
//判斷插入本身是否存在問題(如果插入元素位置比整張表的長度+1還大(如果相等,是尾隨的情況),或者插入的位置本身不存在,程序作爲提示並自動退出)
if (add>t.length+1||add<1)
{
printf("插入位置有問題");
return t;
}
//做插入操作時,首先需要看順序表是否有多餘的存儲空間提供給插入的元素,如果沒有,需要申請
if (t.length==t.size)
{
t.head=(int *)realloc(t.head, (t.size+1)*sizeof(int));
if (!t.head)
{
printf("存儲分配失敗");
return t;
}
t.size+=1;
}
//插入操作,需要將從插入位置開始的後續元素,從最後面一個元素逐個後移。
for (int i=t.length-1; i>=add-1; i--)
{
t.head[i+1]=t.head[i];
}
//後移完成後,直接將所需插入元素,添加到順序表的相應位置
t.head[add-1]=elem;
//由於添加了元素,所以長度+1
t.length++;
return t;
}
順序表刪除元素
table delTable(table t,int add)
{
if (add>t.length || add<1)
{
printf("被刪除元素的位置有誤");
exit(0);
}
//刪除操作
for (int i=add; i<t.length; i++)
{
//找到需要刪除的元素,使其後面的元素前移一位
t.head[i-1]=t.head[i];
}
t.length--;
return t;
}
順序表查找元素
//查找函數,其中,elem表示要查找的數據元素的值
int selectTable(table t,int elem)
{
for (int i=0; i<t.length; i++)
{
if (t.head[i]==elem)
{
return i+1;
}
}
//如果查找失敗,返回-1
return -1;
}
順序表修改元素
//更改函數,其中,elem爲要更改的元素,newElem爲新的數據元素
table amendTable(table t,int elem,int newElem)
{
//先查找到該元素位置下標
int add=selectTable(t, elem);
//由於返回的是元素在順序表中的位置,所以-1就是該元素在數組中的下標
t.head[add-1]=newElem;
return t;
}
鏈表
概念
鏈表,別名鏈式存儲結構或單鏈表,用於存儲邏輯關係爲 “一對一” 的數據。與順序表不同,鏈表不限制數據的物理存儲狀態,換句話說,使用鏈表存儲的數據元素,其物理存儲位置是隨機的。
Tips:鏈表中每個數據的存儲都由以下兩部分組成:
數據元素本身,其所在的區域稱爲數據域;
指向直接後繼元素的指針,所在的區域稱爲指針域
一個完整的鏈表需要由以下幾部分構成:
頭指針:一個普通的指針,它的特點是永遠指向鏈表第一個節點的位置。很明顯,頭指針用於指明鏈表的位置,便於後期找到鏈表並使用表中的數據;
節點:鏈表中的節點又細分爲頭節點、首元節點和其他節點:
頭節點:其實就是一個不存任何數據的空節點,通常作爲鏈表的第一個節點。對於鏈表來說,頭節點不是必須的,它的作用只是爲了方便解決某些實際問題;
首元節點:由於頭節點(也就是空節點)的緣故,鏈表中稱第一個存有數據的節點爲首元節點。首元節點只是對鏈表中第一個存有數據節點的一個稱謂,沒有實際意義;
其他節點:鏈表中其他的節點
注意:鏈表中有頭節點時,頭指針指向頭節點;反之,若鏈表中沒有頭節點,則頭指針指向首元節點。
聲明鏈表
typedef struct Link
{
char elem; //代表數據域
struct Link * next; //代表指針域,指向直接後繼元素
}link;
//link爲節點名,每個節點都是一個 link 結構體
//由於指針域中的指針要指向的也是一個節點,因此要聲明爲 Link 類型
創建鏈表
link * initLink()
{
//創建一個頭結點
link * p=(link*)malloc(sizeof(link));
//聲明一個指針指向頭結點,
link * temp=p;
//生成鏈表
for (int i=1; i<5; i++)
{
link *a=(link*)malloc(sizeof(link));
a->elem=i;
a->next=NULL;
//此處temp->next其實就是head->next,temp->next此時指向的是a的地址。就是head->next指向a的地址
temp->next=a;
//將a的地址值賦值給temp,即temp指向了節點a。
temp=temp->next;
}
return p;
}
帶頭節點的鏈表
link * initLink()
{
//創建一個頭結點
link * p=(link*)malloc(sizeof(link));
//聲明一個指針指向頭結點,
link * temp=p;
//生成鏈表
for (int i=1; i<5; i++)
{
link *a=(link*)malloc(sizeof(link));
a->elem=i;
a->next=NULL;
//此處temp->next其實就是head->next,temp->next此時指向的是a的地址。就是head->next指向a的地址
temp->next=a;
//將a的地址值賦值給temp,即temp指向了節點a。
temp=temp->next;
}
return p;
}
不帶頭結點的鏈表(帶首元節點)
link * initLink()
{
link * p=NULL; //創建頭指針
link * temp = (link*)malloc(sizeof(link));//創建首元節點
temp->elem = 1; //首元節點先初始化
temp->next = NULL;
p = temp; //頭指針指向首元節點
//從第二個節點開始創建
for (int i=2; i<5; i++)
{
//創建一個新節點並初始化
link *a=(link*)malloc(sizeof(link));
a->elem=i;
a->next=NULL;
//將temp節點與新建立的a節點建立邏輯關係
temp->next=a;
//指針temp每次都指向新鏈表的最後一個節點,
//其實就是 a節點,這裏寫temp=a也對
temp=temp->next;
}
//返回建立的節點,只返回頭指針 p即可,通過頭指針即可找到整個鏈表
return p;
}
鏈表插入數據
插入思想:
雖然新元素的插入位置不固定,但是鏈表插入元素的思想是固定的,只需做以下兩步操作,即可將新元素插入到指定的位置:
將新結點的 next 指針指向插入位置後的結點;
將插入位置前結點的 next 指針指向插入結點;
//p爲原鏈表,elem表示新數據元素,add表示新元素要插入的位置
link * insertElem(link * p,int elem,int add)
{
link * temp=p;//創建臨時結點temp
for (int i=1; i<add; i++) //首先找到要插入位置的上一個結點
{
if (temp==NULL) {
printf("插入位置無效\n");
return p;
}
temp=temp->next;
}
//創建插入結點c
link * c=(link*)malloc(sizeof(link));
c->elem=elem;
c->next=temp->next; //向鏈表中插入結點
temp->next=c;
return p;
}
鏈表刪除數據
//p爲原鏈表,add爲要刪除元素的值
link * delElem(link * p,int add)
{
link * temp=p;
//temp指向被刪除結點的上一個結點
for (int i=1; i<add; i++)
{
temp=temp->next;
}
//單獨設置一個指針指向被刪除結點,以防丟失
link * del=temp->next;
//刪除某個結點的方法就是更改前一個結點的指針域
temp->next=temp->next->next;
free(del);//手動釋放該結點,防止內存泄漏
return p;
}
鏈表查找數據
//p爲原鏈表,elem表示被查找元素、
int selectElem(link * p,int elem)
{
//新建一個指針t,初始化爲頭指針 p
link * t=p;
int i=1;
//由於頭節點的存在,因此while中的判斷爲t->next
while (t->next)
{
t=t->next;
if (t->elem==elem)
{
return i;
}
i++;
}
//程序執行至此處,表示查找失敗
return -1;
}
鏈表更新數據
//更新函數,其中,add 表示更改結點在鏈表中的位置,newElem 爲新的數據域的值
link *amendElem(link * p,int add,int newElem)
{
link * temp=p;
//在遍歷之前,temp指向首元結點
temp=temp->next;
//遍歷到被刪除結點
for (int i=1; i<add; i++)
{
temp=temp->next;
}
temp->elem=newElem;
return p;
}
計算鏈表的大小
size_t LinkListSize(LP_RECSEGMENT_INFO_V20 *head)
{
if(head==NULL)
{
return 0;
}
LP_RECSEGMENT_INFO_V20 *cur=head;
size_t size=0;
for(;*cur!=NULL;*cur=(*cur)->pNext)
{
size++;
}
if(*cur==NULL)
{
return 0;
}
return size;
}
struct Node
{
int data ;
Node *next ;
};
typedef struct Node Node ;
(1)已知鏈表的頭結點head,寫一個函數把這個鏈表逆序 ( Intel)
Node * ReverseList(Node *head) //鏈表逆序
{
if ( head == NULL || head->next == NULL )
return head;
Node *p1 = head ;
Node *p2 = p1->next ;
Node *p3 = p2->next ;
p1->next = NULL ;
while ( p3 != NULL )
{
p2->next = p1 ;
p1 = p2 ;
p2 = p3 ;
p3 = p3->next ;
}
p2->next = p1 ;
head = p2 ;
return head ;
}
(2)已知兩個鏈表head1 和head2 各自有序,請把它們合併成一個鏈表依然有序。(保留所有結點,即便大小相同)
Node * Merge(Node *head1 , Node *head2)
{
if ( head1 == NULL)
return head2 ;
if ( head2 == NULL)
return head1 ;
Node *head = NULL ;
Node *p1 = NULL;
Node *p2 = NULL;
if ( head1->data < head2->data )
{
head = head1 ;
p1 = head1->next;
p2 = head2 ;
}
else
{
head = head2 ;
p2 = head2->next ;
p1 = head1 ;
}
Node *pcurrent = head ;
while ( p1 != NULL && p2 != NULL)
{
if ( p1->data <= p2->data )
{
pcurrent->next = p1 ;
pcurrent = p1 ;
p1 = p1->next ;
}
else
{
pcurrent->next = p2 ;
pcurrent = p2 ;
p2 = p2->next ;
}
}
if ( p1 != NULL )
pcurrent->next = p1 ;
if ( p2 != NULL )
pcurrent->next = p2 ;
return head ;
}
(3)已知兩個鏈表head1 和head2 各自有序,請把它們合併成一個鏈表依然有序,這次要求用遞歸方法進行。(Autodesk)
Node * MergeRecursive(Node *head1 , Node *head2)
{
if ( head1 == NULL )
return head2 ;
if ( head2 == NULL)
return head1 ;
Node *head = NULL ;
if ( head1->data < head2->data )
{
head = head1 ;
head->next = MergeRecursive(head1->next,head2);
}
else
{
head = head2 ;
head->next = MergeRecursive(head1,head2->next);
}
return head ;
}