數據結構筆記二:線性表


線性表

線性表的定義

線性表是具有相同數據類型的\(n(n\ge 0)\)​個數據元素的有限序列,其中\(n\)​為表長,當\(n=0\)​時線性表是一個空表。若用\(L\)​命名線性表,則其一般表示為

\[L(a_1,a_2,...,a_i,a_{i+1},...,a_n) \]

\(a_i\)是線性表中的“第i個”元素線性表中的位序(位序從1開始,數組下標從0開始)

\(a_1\)是表頭元素;\(a_n\)是表尾元素

除第一個元素外,每個元素有且僅有一個直接前驅;除最后一個元素外,每個元素有且僅有一個直接后繼。

線性表的基本操作

InitList(&L ):         //初始化表。構造一個空的線性表L,分配內存空間
DestoryList(&L ):      //銷毀操作。銷毀線性表,並釋放線性表L所占用的內容空間。

ListInsert(&L,i,e):	   //插入操作。在表L中的第i個位置上指定元素e
ListDelete(&L,i,&e);   //刪除操作。刪除表中L中第i個位置的元素,並用e返回刪除元素的值

LocateElem(L,e);	   //按值查找操作。在表L中查找具有給定關鍵字的元素。
GetElem(L,i);		   //按位查找操作。獲取表L中第i個位置的元素的值。

Length(L);			   //求表長。返回線性表L的長度,即L中數據元素的個數
PrintList();            //輸出操作。按前后順序輸出線性表L的所有元素值
Empty(L);			  //判空操作。若L為空表,則返回true,否則返回false;

線性表的順序表示

順序表的定義

順序表——用順序存儲的方式實現線性表。

image-20210807204113026

//定義
//靜態分配
#define MaxSize 10			//定義最大長度
typedef struct{
    Elemtype data[Maxsize];	 //順序表的元素
    int length;	             //順序表的當前長度 
}Sqlist;

//動態分配
#define InitSize  100		//表長度的初始定義
typedef struct{
    Elemtype *data;	 		//指示動態分配數組的知識
    int MaxSize,length;	     //數組的最大容量和當前個數
}SeqList;


//c動態分配語句
L.data=(Elemtype*)malloc(sizeof(Elemtype)*InitSize);

//c++動態分配語句
L.data=new Elemtype[InitSize];

順序表的特定:

  1. 隨機訪問,即可以在\(O(1)\)s時間內找到第i個元素
  2. 存儲密度高,每個節點只存儲數據元素
  3. 拓展容量不方便(基表采用動態分配的方式實現,拓展長度的時間復雜度也比較高)
  4. 插入,刪除操作不方便,需要移動大量元素

順序表上的基本操作的實現

插入操作

//靜態分配下
bool ListInsert(SqList &L,int i,Elemtype e)
{
    if(i<1||i>L.length+1)			//判定i的范圍是否有效
        return false;
    if(L.length>=MaxSize)			//當前存儲空間已滿,不能插入
        return false;
    for(int j=L.length;j>=i;--j)	//后移
        L.data[j]=L.data[j-1];
    L.data[i-1]=e;				
    L.length++;
    return true;
}

最好情況:在表尾插入(即\(i=n+1\)),循環0次;最好時間復雜度為\(O(1)\)

最壞情況:在表頭插入(即\(i=1\)),循環n次;最好時間復雜度為\(O(n)\)

平均情況:假設\(p_i(p_i=1/(n+1))\)是在第\(i\)個位置上插入一個結點的概率,則移動結點的平均次數為

\[\sum^{n+1}_{i=1}p_i(n-i+1)=\frac {1}{n+1} \sum^{n+1}_{i=1}(n-i+1)=\frac {1}{n+1}\frac {n(n+1)}{2}=\frac {n}{2} \]

插入算法的平均時間復雜度為\(O(n)\)

刪除操作

//靜態分配下
bool ListDelete(SqList &L,int i,Elemtype& e)
{
    if(i<1||i>L.length+1)			//判定i的范圍是否有效
        return false;
    e=L.data[i-1];					//賦值給e
    for(int j=i;j<L.length;j++)
       L.data[j-1]=L.data[j];
    L.length--;
    return true;
}

最好情況:刪除表尾元素(即\(i=n\)​),無需移動元素;最好時間復雜度為\(O(1)\)​​

最壞情況:刪除表頭元素(即\(i=1\)),需移動除表頭元素外的所有元素;最好時間復雜度為\(O(n)\)

平均情況:假設\(p_i(p_i=1/n)\)​是在第\(i\)​​個位置上刪除一個結點的概率,則移動結點的平均次數為

\[\sum^{n}_{i=1}p_i(n-i)=\frac {1}{n} \sum^{n}_{i=1}(n-i)=\frac {1}{n}\frac {n(n-1)}{2}=\frac {n-1}{2} \]

刪除算法的平均時間復雜度為\(O(n)\)

查找操作(按值查找)

int LocateELem(SqList L,Elemtype e)
{
    int i=0;
    for(i=0;i<L.Length;++i)
        if(L.data[i]==0)
            return i+1;	·		 //下標為i的元素值等於e,返回其位序i+1
    return 0;					//退出循環,說明查找識別
}

最好情況:查找的元素就在表頭,僅需比較一次;最好時間復雜度為\(O(1)\)

最壞情況:查找的元素在表尾(或不存在)時,需比較n次;最好時間復雜度為\(O(n)\)

平均情況:假設\(p_i(p_i=1/n)\)是查找的元素在第\(i\)​個位置的概率,則移動結點的平均次數為

\[\sum^{n}_{i=1}p_i\times i=\frac {1}{n} \sum^{n}_{i=1}i=\frac {1}{n}\frac {n(n+1)}{2}=\frac {n+1}{2} \]

查找算法的平均時間復雜度為\(O(n)\)

線性表的鏈式表示

單鏈表的定義

//單鏈表中的結點類型
typedef struct Lnode{
    Elemtype data;			//數據域
    struct Lnode* next;		//指針域
}Lnode,*LinkList;

要表示一個單鏈表時,只需聲明一個頭指針L,指向單鏈表的第一個結點

為了操作上的方便,在單鏈表第一個結點之前附加一個頭結點。頭節點的數據域可以不設任何信息。引入頭結點后:

  1. 在鏈表的第一個位置上的操作與其他位置的一致,無須進行特殊處理
  2. 無論鏈表為空,其頭指針都指向頭結點的非空指針(空表中頭節點的指針域為空),實現空表與非空表的處理統一。

單鏈表的基本操作

建立操作

頭插法

該方法從一個空表開始,生成一個新結點,並將讀取到的數據存放到新結點的數據域中,然后講新結點插入到當前鏈表的表頭。

image-20210811201305880

//帶頭結點
LinkList List_HeadInsert(LinkList &L)
{
    LNode *s;int x;
    L=(LinkList)malloc(sizeof(LNode));		//創建頭結點
    L->next=NULL;						  //初始為空鏈表
    scanf("%d",&x);						  //輸入結點的值
    whille(x!=9999)						  //輸入9999表示結點
    {
        s=(LNode*)malloc(sizeof(LNode));
        s-data=x;
        s-next=L->next;					  //將新結點插入表中,L為頭結點
        L-next=s;
        scanf("%d",&x);
    }
    return L:
}

平均時間復雜度為\(O(n)\)

頭插法應用:鏈表的逆置

尾插法

image-20210811201321828

該方法將新結點插入到當前鏈表的表尾,為此必須增加一個尾指針人,使其始終指向當前鏈表的尾結點。

//帶頭結點
LinkList List_TailInsert(LinkList &L)
{
    int x;+									
	LNode *s,*r=L;						 //r為表尾指針
    scanf("%d",&x);						  //輸入結點的值
    whille(x!=9999)						  //輸入9999表示結點
    {
        s=(LNode*)malloc(sizeof(LNode));
        s-data=x;
        r->next=s;
        r=s;							  //r指向新的表尾結點
        scanf("%d",&x);
    }
    r->next=NULL;						   //尾指針置空
    return L:
}

平均時間復雜度為\(O(n)\)

查找操作

按位(序號)查找

LNode *GetElem(LinkList L,int i){    int j=1;				//計數,初始為1    LNode *p=L->next;		 //頭結點賦值給p    if(i==0)        return L;			//若i等於0,則返回頭結點    if(i<1)        return NULL;		//若i無效,則返回NUll    while(p&&j<i)			//第一個結點開始,查找第i個結點    {        p=p->next;        j++;    }    return p;				//返回第i個結點的指針,若i大於表長,則返回NULL}

平均時間復雜度為\(O(n)\)

按值查找

LNode *LocateElem(LinkList L,ELemtype e){    LNode *p=L->next;    while(p!=NULL&&p->data!=e)		//第一個結點開始查找data域為e的結點        p=p->next;    return p;				//找到后返回該結點指針,否則返回NULL}

平均時間復雜度為\(O(n)\)

插入操作

后插操作:

image-20210811201336312

p=GetElem(L,i-1);				//查找插入位置的前驅結點s->next=p->next;				//第二步p->next=s;					    //第三步

時間復雜度為\(O(n)\)

擴展:對某一結點進行前插操作

s->next=p->next;				//修改指針域,不能顛倒p->next=s;temp=p->data;					//交換數據域部分p->data=s=>data;s->data=temp;

時間復雜度為\(O(1)\)

刪除操作

image-20210811201643934

p=GetElem(L,i-1);				//查找刪除位置的前驅結點q=p->next;					    //令q指向被刪除結點p->next=q->next;				//將*q結點從鏈中“斷開”free(q);					    //釋放結點的存儲空間

時間復雜度為\(O(n)\)

擴展:刪除結點*p

q=p->next;					//令q指向*p的后續結點p->data=p->next->data;		 //和后續結點交換數據域(注意是否為最后一個結點)p->next=q->next;			//將*q結點從鏈中“斷開”free(q);

時間復雜度為\(O(1)\)

雙鏈表的定義

引入前驅指針

image-20210811202846619

//定義雙鏈表結點類型typedef struct DNode{    Elemtype data;						//數據域    struct DNode* prior,*next;			 //前驅和后繼指針 }DNode,*DLinkList;

雙鏈表的基本操作

插入操作

image-20210811202854150

s->next=p->next;			//將結點*s插入到*p之后if(p->next!=NULL)		    //判斷是否為最后一個結點    p->next->prior=p;s->next=p;p->next=s;

刪除操作

image-20210811202904796

//刪除*p后續結點*qp->next=q->next;q->next->priot=p;free(q);

循環鏈表的定義

循環單鏈表

表中最后一個結點的指針不是NULL,,而改為指向頭結點。

判空條件:是否等於頭結點

循環雙鏈表

頭結點的prior指針還要指向表尾結點。

image-20210811203619346

當循環雙鏈表為空表是,其頭結點和prior域與next域都等於L。

具體操作自己學習實現。

靜態鏈表的定義

靜態鏈表借助數組來描述線性表的鏈式存儲結構,結點也有數據域data與指針域next;

這里的指針是結點的相對地址(數組下標),又稱游標。

靜態鏈表要預先分配一塊連續的內存空間。

image-20210811204748663

typedef struct{    Elemtype data;			//存儲數據元素    int next;				//下一個元素的數組下標}SLinkList[MaxSize];		//定義一個結構體數組

優點:增,刪操作不需要大量移動元素

缺點:不能隨機存儲,只能從頭結點開始依次往后查找;容量固定不可變

適用場景:①不支持指針的低級語言 ②數據元素數量固定不變的場景(如操作系統的文件分配表FAT)

順序表和鏈表的對比

存儲結構

image-20210811205253208

基本操作

image-20210811205541297

image-20210811205803791

image-20210811210004944

image-20210811210112730

選擇

image-20210811210339643


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM