title: 第二章
grammar_cjkRuby: true
[TOC]
一.線性表的概念及運算方法
線性表的邏輯結構
線性表是n個數據元素的有限序列線性表中數據元素之間的關系是一對一的關系,即除了第一個和最后一個數據元素之外,其它數據元素都是首尾相接的。
- 根據他們之間的關系,可以排成一個線性序列,記作:(a1,a2,...,an)
- 這里的a屬于同一數據對象,具有相同的數據類型。
- 線性表的特點
- 同一性:線性表由同一數據類型組成,每一個a[i]必須屬于同一數據對象
- 有窮性:線性表由有限個數據元素組成,表長度就是表中數據元素的個數
- 有序性:線性表中相鄰的兩個元素之間存在著序偶關系<a[i],a[i+1]>.
線性表的運算
線性表可以進行增刪改查,也可以加長或者剪短線性表的長度
1. InitList(L),線性表初始化,創造一個新的線性表
2. ListLength(L),求線性表的長度,返回線性表L中數據元素的個數
3. GetElem(L,i,x),用x返回線性表中第i數據元素的值
4. LocationElem(L,x),按值查找,確定數據元素x在表中的位置
5. ListInsert(L,i,x),插入操作,在線性表L中第i個位置之前插入一個新元素x,L的長度加一。
6. LIstDelete(L,i),刪除操作,刪除線性表L中的第i個元素,線性表的長度減一。
7. ListEmpty(L),判斷線性表是否為空,是空則返回true,不是空則返回false。
8. ClearList(L),將已知的線性表置成空表。
9. DestoryList(L),銷毀線性表。
二.線性表的順序儲存
1. 順序表
- 順序儲存:順序儲存是指在內存中用一塊連續的儲存空間按順序儲存線性表的各個元素。采取順序儲存結構的儲存表叫做順序表。
- 線性表的這種機內表示稱為線性表的順序儲存結構或順序映像
- **只要知道了順序表的起始位置,線性表中任意數據元素都可以隨機存取,順序表是一種隨機存取結構
- 線性表的順序儲存結構可以描述如下:
# define MAXSIZE<線性表可能達到的最大長度>
typedef int ElemType;
typedef struct{
ElemType elem[MAXSIZE];
int length; //線性表長度
}seqList;
定義一個順序表:
seqList *L;
順序表的長度為L->length,數據元素是L->elem[1]~L->elem[length],因C語言中數組的下標是從0開始的,為了一線性表中的數據元素的位序保持一致,可不使用數組下標為0的單元,下標的取值范圍是1<=i<=MAXSIZE-1
2. 順序表的基本運算
(1). 順序表的初始化
創造一個空表,將表長length設為0,表示表中沒有數據。
調用方法為:
Init_SeqList(&L);
(2).順序表的插入
- 操作步驟如下:
- 將a(n)~a(i)按從后向前的順序向下移動,為新元素讓出位置。
- 將x放在空出的第i個位置。
- 修改表長。
(3). 順序表的刪除
- 操作步驟
- 將a(i+1)~a(n)依次向上移動;
- 將length值減1.
(4). 在順序表中按值查找
線性表中的按值查找是指在線性表中查找第一個與給定值x相等的數據元素。
- 算法思想
- 從第一個元素a(1)起依次和x比較,直到找到一個與x的值相等的數據元素,返回它在序列表中的序號;若查遍整個線性表都沒有找到與x相等的元素,則返回FALSE。
順序表的特點
** 線性表順序儲存的特點是用物理位置上的相鄰來表示數據元素之間邏輯相鄰的關系,儲存密度高,且能隨機的存儲數據;但在進行插入,刪除時會移動大量數據元素,運行效率低。而且順序表需要事先分配儲存空間若N的值變化較大時,則存儲規模難以確定,估計過大會導致存儲空間的浪費。**
三. 線性表的鏈式結構
1.單鏈表
單鏈表的定義
- 每一個單鏈表都是由一個個節點組成,節點包括兩部分,儲存數據的數據域和儲存當前節點的下一個節點的地址信息。
- 節點定義如下:
typedef struct node
{
ElemType date; //數據域
struct node *next; //指針域
} LNode,*LinkList;
頭結點,頭指針
- 在單鏈表的第一個節點之前會附加一個節點,叫做頭節點,頭結點的數據域可以儲存標題,表長等信息,頭結點的指針域儲存第一個節點的首地址,頭結點由頭指針指向。
由于最后一個節點沒有后繼節點,所以他的指針域為空(NULL)用^表示。 - 頭指針變量的定義:LinkList H;
- 算法中用到的指向某節點的指針變量的聲明:LNode *p;
- 語句p=(LinkList)malloc(sizeof(LNode));表示申請一個LNode類型的儲存空間的操作,并且將值賦給變量P
2.單鏈表的基本運算
建立單鏈表
單鏈表的建立有兩種方法,頭插法和尾插法
頭插法
- 首先申請一個頭結點,并且將頭結點的指針域設為空
- 每讀入一個數據都申請一個節點,都插入鏈表的頭節點之后。
- 因為是在鏈表的頭部插入節點,所以數據讀入順序和線性表中的邏輯順序正好相反。
建立單鏈表的例子
#include<stdio.h>
#include<stdlib.h>
typedef struct student //定義一個結構體-》抽象數據類型
{
int name;
struct student *next;
}list,*Llist; //list指結構體名 , *Llist指這個結構體的指針,相當于強制類型轉換中的 (list *)
//創建一個節點(結構體),用來保存數據
Llist CreatList() //創建一個單鏈表,名字叫做 CreatList
{
int num=1;
Llist head = (list *)malloc(sizeof(list)); //定義一個頭指針,并且給頭指針分配儲存空間
head->next=NULL; //讓頭指針指向為空
list *s; //定義數據類型為list的指針*s用來做節點
int x;
scanf("%d",&x); //輸入需要保存的數據
while(x!=-1) //
{
s = (Llist)malloc(sizeof(list)); //為新建立的節點分配空間
s->name = x;
s->next = head->next; //新節點的指針域指向空
head->next=s; //讓頭結點指向新節點
printf("輸出次數%d",num);
num++;
scanf("%d",&x);
}
return head; //所有的鏈表最后都要返回頭指針
}
int main()
{
printf("shuru");
CreatList(); //調用鏈表
}
尾插法
在單鏈表的尾部插入節點建立單鏈表
- 由于每次是將新節點插入到鏈表的尾部,所以增加一個尾指針r來始終指向鏈表中的尾節點以便能夠將結點插入到鏈表的尾部。
- 首先申請一個頭結點,并將頭結點指針域設空,頭指針h和尾指針r都指向頭結點
- 然后按線性表中元素的順序依次讀入數據元素,如果不是結束標志,則申請結點
- 將新的結點插入r所指結點的后面,并使r指向新結點。
尾插法建立鏈表代碼實現
2. 求表長
算法思路:設一個指針p和計數器j,初始化使p指向結點H,j=0.若p所指的結點還有后繼結點,p向后移動,計數器加1,重復上述過程,直到p->next=NULL為止。
3. 查找操作
- 按序號查找Get_Linklist(H,k)
思路:從鏈表的第一個結點起判斷當前結點是否是第k個,如果是,則返回該節點的指針,否則繼續查找下一個直到結束為止。沒有第k個結點時返回空。
- 按值x查找(及查找x節點的位置)
思路:從鏈表的第一個結點起判斷當前結點的值是否等于x,如果是,則返回該節點的指針,否則繼續查找下一個直到結束為止。沒有第k個結點時返回空。
4. 插入操作
設p指向單鏈表中的某結點,s指向待插入的新結點,將*s插入到 *p的后面,插入過程如下:
(1): s->next=p->next;
(2): p->next=s;
q=H;
while(q->next!=p)
q=q->next; //找*p的直接前驅
s->next=q->next;
q->next=s; //插入
- 將新結點*s插入到i個結點的位置上,即插入到a(i-1)與a(i)之間。算法思路如下:
- 查找第i-1個結點,若存在,繼續2,否則結束。
- 創建新結點
- 將新節點插入,結束。
5. 刪除操作
設p指向單鏈表中要刪除的結點,首先找到*p的前驅結點 *q,然后完成刪除操作,操作過程如下:
- q->next=p->next;
- free(p);
- 刪除鏈表中的第i個結點。算法思路如下
- 查找第i-1個結點,若存在,則繼續2,若不存在則結束;
- 若存在第i個結點,則繼續3,否則,結束;
- 刪除第i個結點否則結束
3. 循環鏈表
- 在單鏈表的基礎上,將其最后一個結點的指針域指向該鏈表頭結點,使得鏈表頭尾結點相連,就構成了單循環鏈表。
- 特點:
- 循環鏈表的特點是無須增加存儲量,僅對表的鏈接方式稍作改變,即可使得表處理更加方便靈活。①循環鏈表中沒有NULL指針。涉及遍歷操作時,其終止條件就不再是像非循環鏈表那樣判別p或p->next是否為空,而是判別它們是否等于某一指定指針,如頭指針或尾指針等。
- 在單鏈表中,從一已知結點出發,只能訪問到該結點及其后續結點,無法找到該結點之前的其它結點。而在單循環鏈表中,從任一結點出發都可訪問到表中所有結點,這一優點使某些運算在單循環鏈表上易于實現。
4. 雙向鏈表
- 雙向鏈表也叫雙鏈表,是鏈表的一種,它的每個數據結點中都有兩個指針,分別指向直接后繼和直接前驅。所以,從雙向鏈表中的任意一個結點開始,都可以很方便地訪問它的前驅結點和后繼結點。一般我們都構造雙向循環鏈表。
- 結點的定義:
typedef struct DuLNode
{
ElemType data;
struct DuLNode *prior,*next;
}DuLNode,*DuLinkList;
- 雙向鏈表的定義:
帶頭結點的雙向循環鏈表的基本操作
void InitList(DuLinkList L)
{ /* 產生空的雙向循環鏈表L */
L=(DuLinkList)malloc(sizeof(DuLNode));
if(L)
L->next=L->prior=L;
else
exit(OVERFLOW);
}
靜態鏈表
- 用數組描述的鏈表,即稱為靜態鏈表。在C語言中,靜態鏈表的表現形式即為結構體數組,結構體變量包括數據域data和游標CUR。
- 靜態鏈表結點的定義:
#define MAXSIZE 100;
typedef struct
{
ElemType data;
int cur;
}component,SLinkList[MAXSIZE];
- 靜態鏈表的優點:
這種存儲結構,仍需要預先分配一個較大的空間,但在作為線性表的插入和刪除操作時不需移動元素,僅需修改指針,故仍具有鏈式存儲結構的主要優點。
- 靜態鏈表和動態鏈表的區別:
有些高級語言中沒有“指針”數據類型,只能用數組來模擬線性鏈表的結構,
數組元素中的指針“域”存放的不是元素在內存中的真實地址,而是在數組中的位置。這樣的鏈表
稱為靜態鏈表。而通過定義一個較大的結構體數組來作為備用結點空間(即存儲池),
每個結點應至少含有兩個域:data域和cursor域。
四. 順序表和鏈表的比較
- 順序存儲表示是將數據元素存放于一個連續的存儲空間中,實現順序存取或(按下標)直接存取。它的存儲效率高,存取速度快。
- 順序表的空間大小一經定義,在程序整個運行期間不會發生改變,因此,不易擴充
- 由于在插入或刪除時,為保持原有次序,平均需要移動一半(或近一半)元素,修改效率不高。
- 1 順序表和鏈表的時間性能比較
所謂時間性能是指實現基于這種存儲結構的基本運算(即算法)的時間復雜度。
像取出線性表中第 i 個元素這樣的按位置隨機訪問的操作,使用順序表更快一些;取前趨和后繼結點的操作在順序表中可以很容易調整當前的位置向前或向后,因此這兩種操作的時間為 O (1) ;相比之下,單鏈表不能直接訪問上述的元素,按位置訪問只能從表頭開始,直到找到那個特定的位置,所需要的平均時間為 O ( n ) 。
給出指向鏈表中某個合適位置的指針后,插入和刪除操作所需的時間僅為 O ( 1 ),而順序表進行插入和刪除操作需移動近乎表長一半的元素,需要的平均時間為 O ( n ) 。這在線性表中元素個數較多時,特別是當每個元素占用的空間較多時,移動元素的時間開銷很大。對于許多應用,插入和刪除是最主要的操作,因此它們的時間效率是舉足輕重的,僅就這個原因而言,鏈表經常比順序表更好。
作為一般規律,若線性表需頻繁查找卻很少進行插入和刪除操作,或其操作和“數據元素在線性表中的位置”密切相關時,宜采用順序表作為存儲結構;若線性表需頻繁進行插入和刪除操作時,則宜采用鏈表做存儲結構。
- 2 順序表和鏈表的空間性能比較
所謂空間性能是指這種存儲結構所占用的存儲空間的大小。
首先定義結點的 存儲密度 。
#include<stdio.h>
#include<stdlib.h>
struct Lnode/*定義鏈表*/
{
int number;
int password;
struct Lnode *next;
}Lnode,*p,*q,*head;
int main(void)
{
int n;/*n個人*/
int i;
int m;/*初始報數上限值*/
int j;
printf("pleaseenterthenumberofpeoplen:");/*輸入測試人的數量*/
scanf("%d",&n);
loop:if(n<=0||n>30)/*檢驗n是否滿足要求,如不滿足重新輸入n值*/
{ printf("\nniserorr!\n\n");
printf("pleaseenterthenumberofpeopleagainn:");
scanf("%d",&n);
goto loop;
}
for(i=1;i<=n;i++)/*建立單鏈表*/
{
if(i==1)
{
head=p=(struct Lnode*)malloc(sizeof(struct Lnode));
}
else
{
q=(struct Lnode*)malloc(sizeof(struct Lnode));
p->next=q;
p=q;
}
printf("pleaseenterthe%dpeople'spassword:",i);/*輸入每個人所持有的密碼值*/
scanf("%d",&(p->password));
p->number=i;
}
p->next=head;/*形成循環鏈表*/
p=head;
printf("pleaseenterthenumberm:");
scanf("%d",&m);
printf("Thepasswordis:\n");
for(j=1;j<=n;j++)/*輸出各人的編號*/
{
for(i=1;i<m;i++,p=p->next);
m=p->password;
printf("%d",p->number);
p->number=p->next->number;/*刪除報m的節點*/
p->password=p->next->password;
q=p->next;
p->next=p->next->next;
free(q);
}
printf("\n\n");
system("pause");/*等待按任意鍵退出*/
}
/*
一元多多項式的加法
1.先創建鏈表,存儲多項式
2.輸出多項式
3.兩個多項式相加
4.輸出多項式
*/
# include <stdio.h>
# include <malloc.h>
typedef struct dxs //多項式節點
{
float coe; //系數
int exp; //指數
struct dxs * pNext; //指針域
}DXS, * PDXS;
PDXS creat_dxs(); //創建多項式
void traverse(PDXS pHead); //遍歷多項式鏈表
PDXS add(PDXS Da, PDXS Db); //多項式求和
int main(void)
{
//用鏈表結構,創建兩個多項式
PDXS Da = creat_dxs();
traverse(Da);
PDXS Db = creat_dxs();
traverse(Db);
//求兩個多項式的加法
PDXS Dj = add(Da, Db);
traverse(Dj);
return 0;
}
PDXS creat_dxs()
{
PDXS pHead = (PDXS)malloc(sizeof(DXS)); //創建頭結點
pHead->pNext = NULL; //尾指針
PDXS pTail = pHead;
PDXS pNew = NULL;
int len;
float c;
int e;
int i;
printf("輸入多項式的項數:len = ");
scanf("%d", &len);
for(i = 0; i < len; i++)
{
printf("分別輸入第%d項的系數c、指數e:", i+1);
scanf("%f%d", &c, &e);
pNew = (PDXS)malloc(sizeof(DXS));
//多項式項,系數、指數
pNew->coe = c;
pNew->exp = e;
pNew->pNext = NULL;
pTail->pNext = pNew;
pTail = pNew;
}
return pHead;
}
//遍歷鏈表
void traverse(PDXS pHead)
{
PDXS p = pHead->pNext; //首節點
while(p != NULL)
{
printf("(%.2f %d), ", p->coe, p->exp);
p = p->pNext;
}
printf("\n");
}
//多項式相加
PDXS add(PDXS Da, PDXS Db)
{
PDXS Dj = (PDXS)malloc(sizeof(DXS)); //和的頭結點
Dj->pNext = NULL;
PDXS pTail = Dj; //和的尾節點
PDXS Dah = Da->pNext; //指向多項式的首節點
PDXS Dbh = Db->pNext;
//循環遍歷多項式A,B
while(Dah && Dbh)
{
//比較當前兩節點的指數
//當前 A項節點指數 < B項節點指數
if(Dah->exp < Dbh->exp)
{
pTail->pNext = Dah; //將此A項加入和鏈表中
pTail = Dah;
Dah = Dah->pNext; //A多項式向后遍歷
}
//當前 A項節點指數 < B項節點指數
else if(Dah->exp > Dbh->exp)
{
pTail->pNext = Dbh; //將此B項加入和鏈表中
pTail = Dbh;
Dbh = Dbh->pNext; // //B多項式向后遍歷
}
//如果兩節點的指數相等
else
{
//當前指數的系數和不為0
//A項中保存系數和,把此A項加入和鏈表中
if(0 != (Dah->coe + Dbh->coe))
{
Dah->coe = Dah->coe + Dbh->coe;
pTail->pNext = Dah;
pTail = Dah;
}
//A,B都向后遍歷
Dah = Dah->pNext;
Dbh = Dbh->pNext;
}
}
//插入剩余段
if(Dah != NULL)
{
pTail->pNext = Dah;
}
if(Dbh != NULL)
{
pTail->pNext = Dbh;
}
return Dj;
}