1. 程式人生 > 實用技巧 >2.0 線性表

2.0 線性表


title: 資料結構 | 線性表
date: 2019-12-03 17:40:48
tags: 資料結構


線性表的順序和鏈式結構
線性連結串列、迴圈連結串列、雙向連結串列

主要內容

線性結構特點
在非空有限集內,存在唯一始末元素、除頭全有唯一前驅,除尾全有唯一後繼。

2.1 線性表的型別定義
2.2 線性表的順序表示與實現
2.3 線性表的鏈式表示與實現
2.3.1 線性連結串列
2.3.2 迴圈連結串列
2.3.3 雙向連結串列

線性表的邏輯結構

定義

一個線性表是n個數據元素的有限序列

特點

1.線性表中所有元素的性質相同。

2.除第一個和最後一個數據元素之外,其它資料元素有且僅有一個前驅和一個後繼。第一個資料元素無前驅,最後一個數據元素無後繼。

3.資料元素在表中的位置只取決於它自身的序號

ADT

ADT List{
	資料物件:D={ai|ai∈ElemSet,i=1,2,…,n,n≥0}
	資料關係:R={<ai-1,ai>|ai,ai-1∈D,1=2,…,n}
	基本操作:
		InitList( &L);
		DestroyList(&L);
		ClearList(&L);
		ListEmpty(L);
		ListLength(L);
		GetElement(L,i,&e);
		LocateElement(L,e,compare( ))
		PriorElement(L,cur_e,&pre_e)
		NextElement(L,cur_e,&next_e)
		ListInsert(&L,i,e);
		ListDelete(&L,i,&e);
		ListTraverse(L,visit( ))
}ADT List

例題

線性表合併

假設有兩個集合A和B分別用兩個線性表LA和LB表示,現要求一個新的集合A=A∪B。

void union( List &La, List Lb)
{
    // 將所有線上性表Lb中但不在La中的資料元素插入到La中
	La_Len = ListLength( La ); // 求線性表的長度
	Lb_Len = ListLength( Lb );
	for( i = 1; i <= Lb_Len; i++)
	{
	     GetElem( Lb, i, e); // 取Lb中第i個數據元素賦給e
	     if( !LocateElem( La, e, equal))
	         ListInsert( La, ++La_Len, e); 
             // La中不存在和 e 相同的資料元素,則插入之
       }
} // union

時間複雜度:
O(ListLength( La ) * ListLength( Lb ))

非遞減線性表La,Lb的合併

void MergeList( List La, List Lb, List &Lc )
{
	InitList( Lc );
	i = j = 1; // i和j分別是La和Lb的序號
	k = 0;     //k是Lc的序號
	La_Len = ListLength( La );
	Lb_Len = ListLength( Lb );
	while((i <= La_Len) && (j <= Lb_Len))
    {
        GetElem( La, i, ai ); 
        GetElem( Lb, j, bj );
    if( ai < = bj ) {
        ListInsert( Lc, ++k, ai); ++i; 
    	}
	else {
        ListInsert( Lc, ++k, bj ); ++j; 
        }
     }
	while( i <= La_Len ){//若La非空,把La剩餘的資料元素插入到Lc中
		GetElem( La, i++, ai );    
		ListInsert( Lc, ++k, ai );
	}
	while( j <= Lb_Len ){//若Lb非空,把La剩餘的資料元素插入到Lc中
		GetElem( Lb, j++, bj );      
		ListInsert( Lc, ++k, bj );
	}
}//MergeList

時間複雜度:
O(ListLength(La ) + ListLength( Lb))

線性表的順序儲存結構

順序表:用一組地址連續的儲存單元存放一個線性表

  • 元素地址計算方法
    LOC(ai)=LOC(a1)+(i-1)*L
    L—一個元素佔用的儲存單元個數
    LOC(ai)—線性表第i個元素的地址
  • 特點
    實現邏輯上相鄰—實體地址相鄰
    實現隨機存取

順序表的型別定義

#define LIST_INIT_SIZE 100  // 線性表儲存空間的初始分配量
#define LISTINCREMENT 10   // 線性表儲存空間的分配增量
typedef  struct{
	ElemType *elem;//儲存空間基址
	int length;   //當前長度
	int listsize //當前分配的儲存容量(以sizeof(ElemType)為單位)
}Sqlist;

順序表的重要操作

初始化順序表

Status InitList_Sq(SqList &L)
{ //構造一個空的順序表L 
	L.elem=(ElemType*)malloc(LIST_INIT_SIZE*sizeof(ElemType));
	if (! L.elem) 
		exit(OVERFLOW);  //儲存分配失敗
	L.length=0;  //空表長度為0
	L.listsize=LIST_INIT_SIZE; //初始儲存容量
	Return OK;
}//InitList_Sq                  

順序表的插入操作

思路

1.輸入是否有效?
2.當前表是否已經滿?
3.移動 i 後的元素
4.插入元素
5.表長增1

程式碼

Status ListInsert_Sq(Sqlist &L, int i, ElemType e)      
{  // 在順序線性表L的第i個位置之前插入新的元素e
   // i的合法值為1<=i<=ListLength_Sq(L) + 1
   if(i<1 || i>L.length+1) 
   	return ERROR;   //i值不合法
   if(L.length>=L.listsize){ // 當前儲存空間已滿,增加分配 
   		newbase=(ElemType*)realloc(L.elem,(L.listsize+LISTINCREMENT)*sizeof(ElemType));
         if(!newbase)
         	exit(OVERFLOW); // 儲存分配失敗
         L.elem=newbase;
         L.listsize+=LISTINCREMENT; // 增加儲存容量
   }
   q=&(L.elem[i-1]);    // q為插入位置
   for(p=&(L.elem[L.length-1]); p>=q; --p)
        *(p+1)=*p;   // 插入位置及之後的元素後移
   *q=e;           // 插入e
   ++L.length;    // 表長增1
   return OK;
}                                                                          

順序表的刪除操作

思路

1.輸入是否有效?
2.刪除(前移元素)
3.表長減1

程式碼

Status ListDelete_Sq(SqList &L,int i,ElemType &e){
//在順序線性表L中刪除第.i個元素,並用e返回其值       
//i的合法值為  1≤i≤L.length
	if((i<1)||(i>L.Length))
		return ERROR;	// i值不合法或表空       
	p=&(L.elem[i-1]);   //p為被刪除元素的位置      
	e=*p;             // 被刪除元素的值賦給e       
	q=L.elem+L.length-1; // 表尾元素的位置       
	for (++p; p<=q;++p)  
		*(p-1)=*p;  //被刪除元素之後的元素前移       
	--L.length; //表長減1      
	return OK;
}//ListDelete_Sq                                       

順序表的查詢操作

int LocateElem(SqList L,ElemType e, Status(*compare)(ElemType,ElemType)){
    ElemType *p;
    int i=1; // i的初值為第1個元素的位序
    p=L.elem; // p的初值為第1個元素的儲存位置
    while(i<=L.length&&!compare(*p++,e))
        ++i;
    if(i<=L.length)
       return i;
    else
       return 0;
 }

順序表的遍歷(函式指標使用說明書)

  • 初始條件:
    順序線性表L已存在
  • 操作結果:
    依次對L的每個資料元素呼叫函式vi()。一旦vi()失敗,則操作失敗。
    若在vi()的形參加'&',表明可通過呼叫vi()改變元素的值。
Status ListTraverse(SqList ,void(*vi)(ElemType&)){
	ElemType *p;
	int i;
	p=L.elem;
	for(i=1;i<=L.length;i++)
		vi(*p++);
	return OK;
}

順序表的一般操作

銷燬順序表

Status DestroyList_Sq ( SqList &L) {      
	if (!L.elem) 
		return ERROR;  // 若表L不存在      
	free (L.elem);	   // 若表L已存在,回收動態分配的儲存空間
	L.elem = null;      
	L.length = 0;      
	L.Listsize = 0;      
	return OK;
}// DetroyList_Sq

置空線性表

 Status ClearList_Sq ( SqList &L) {      
	if (!L.elem) 
		return ERROR; // 若表L不存在      
	L.length = 0;	//若表L已存在,將L置空      
	return OK;
}// ClearList_Sq

判斷空表

Status ListEmpty(SqList L)
 { // 初始條件:順序線性表L已存在。
   //操作結果:若L為空表,則返回TRUE,否則返回FALSE
	if(L.length==0)
		return TRUE;
	else
		return FALSE;
 }

求表長

int ListLength(SqList L){ 
	//初始條件:順序線性表L已存在。
	//操作結果:返回L中資料元素個數
	return L.length;
}

取元素操作

Status GetElem_Sq ( SqList L, int i, ElemType &e ) {      
	if((i< 1)||(i>L.length)) 
		return ERROR;  // i 非法      
	e=L.elem[i-1]; //將順序表中第i 個元素賦值給 e      
	return OK;
}// GetElem_Sq

順序儲存結構的優缺點

優點

  • 邏輯相鄰,物理相鄰
  • 可隨機存取任一元素
  • 儲存空間使用緊湊

缺點

  • 插入、刪除操作需要移動大量的元素
  • 預先分配空間需按最大空間分配,利用不充分
  • 表容量難以擴充

線性表的鏈式儲存結構

特點

  • 用一組任意的儲存單元儲存線性表的資料元素
  • 利用指標實現了用不相鄰的儲存單元存放邏輯上相鄰的元素
  • 每個資料元素ai,除儲存本身資訊外,還需儲存其直接後繼的資訊
  • 結點{
    資料域:元素本身資訊
    指標域:指示直接後繼的儲存位置
    }

線性連結串列的定義

結點中只含一個指標域的連結串列叫線性連結串列,也叫單鏈表。

typedef struct LNode {
     ElemType data;
     struct LNode  *next;
}Lnode,*LinkList;

LNode* p 和 LinkList p
意思一樣,都是建立一個Lnode型的單鏈表
定義出來的都是1個Lnode型的指標變數,通常用他指向頭結點
特別地,注意
LinkList P,Q; /P,Q都是指標 /
LNode P,Q;/只有P是指標* /

  • 頭結點
    在單鏈表第一個結點前附設一個結點叫頭結點
    頭結點指標域為空表示線性表為空

連結串列的重要操作

初始化連結串列

Status InitList_L (LinkList &L) {
	L = (LinkList)malloc(sizeof(LNode));     
	if (!L)
		exit(OVERFLOW);    
	L->next = null;      
	Return OK;
}// InitList_L 

連結串列的按值查詢

Status LocateNode_L(LinkList L,Elemtype key,LNode &e){
	p=L–>next;
	while( p && p–>data!=key)
		p=p–>next;
	if(!p) 
		return ERROR;
	e=p;
	return OK;
}   

連結串列的插入操作

思路

1.尋找第i-1個結點:
順指標向後查詢,直到p指向第i-1個元素或p->next為空
2.分配新空間
3.插入節點

程式碼

Status ListInsert_L(LinkList &L, int i, ElemType e){
 //在帶頭結點的線性連結串列L中第i元素結點之前插入元素e
	p=L; j=0
	while (p&&j<i-1){
		p=p->next; 
		++j;
	}//☆尋找第i-1個元素結點
	if(!p||j>i-1)
		return ERROR; // i小於1  則 j>i-1
	                  // i大於表長+1 則p為空         
	s=(LinkList)malloc(sizeof(LNode)); //分配新結點
	s->data=e;
	s->next=p->next; p->next=s;   //插入新結點
	return OK;
}//LinstInsert_L

連結串列的刪除操作

思路

1.尋找第i-1個結點:
順指標向後查詢,直到p指向第i-1個元素或p->next為空
2.刪除結點(修改其後繼指標)
3.回收(釋放)結點空間

程式碼

Status ListDelete_L(LinkList &L, int i, ElemType &e){
	//在帶頭結點的單鏈線性表L中,刪除第i個元素,並由e返回其值
	p=L; j=0;
	while (p->next&&j<i-1){  //尋找第i-1個結點
		p=p->next; ++j;
	}
	if(!(p->next)||j>i-1)
		return ERROR; //  表中無第i個結點(i不合法)
	                  //  i<1 則 j>i-1
	                  //  i>表長 則 p->next為空
	q=p->next;p->next=q->next;   //刪除結點
	e =q->data; free(q);   //  釋放結點空間
	return OK;
}//LinstDelete_L                    

連結串列的一般操作

取元素操作

Status GetElem_L(LinkList L, int i, ElemType &e){
  //L為帶頭結點的單鏈表的頭指標。
  //當第i個元素存在時,其值賦給e並返回OK,否則返回ERROR
	p=L->next; j=1;  //初始化,p指向第一個結點,j為計數器
	while(p&& j<i){  //順指標向後查詢,直到p指向第i個元素或p為空
		p=p->next;++j;
	}
	if (!p||j>i)
		return ERROR; //第i個元素不存在      
	e=p->data;  //取第i個元素
	return OK;
}//GetElem_L                       

頭插法建立單鏈表

思路

① 建立新節點
② 向新節點中添入內容
③ 使新節點指向鏈頭(第一個元素)
④ 改變頭指標,指向新節點

程式碼

void CreateList_L(LinkList &L, int n) {
	//逆序輸入n個元素的值,建立帶表頭結點的線性連結串列
	L=(LinkList)malloc(sizeof (LNode));
	L->next=NULL; //先建立一個帶頭結點的單鏈表
	for (i=n; i>0;--i){
		p=(LinkList)malloc(sizeof(LNode));//生成新結點
		scanf(&p->data);//輸入元素值
		p->next=L->next;L->next=p; //插入到表頭/
	}
}//CreateList_L

頭插法的幻想圖插了一個想不明白就想兩個節點

尾插法建立單鏈表

思路

① 建立新節點
② 向新節點中添入內容
③ 將新節點鏈入鏈尾
④ 改變尾指標

程式碼

void CreateList_L(LinkList &L, int n) {
	//輸入n個元素的值,建立帶表頭結點的線性連結串列
	L=(LinkList)malloc(sizeof (LNode));
	L->next=NULL; //先建立一個帶頭結點的單鏈表
	r=L;
	for (i=1; i<=n;i++){
		p=(LinkList)malloc(sizeof(LNode));//生成新結點
		scanf(&p->data);//輸入元素值
		p->next=NULL;
		r->next=p;
		r=p; 
	}
}//CreateList_L

歸併2個有序連結串列

void MergeList_L(LinkList &La, LinkList &Lb, LinkList &Lc){
	pa=La->next; 
	pb=Lb->next;   
	Lc=pc=La;  //用La的頭結點作為Lc的頭結點     
	while(pa && pb){         
		if (pa->data<=pb->data){
			pc->next=pa; 
			pc=pa; 
			pa=pa->next;
		}      
		else {
			pc->next=pb;   
			pc=pb;  
			pb=pb->next;
		}  
	}
	pc->next=pa?pa:pb;//插入剩餘段
	free(Lb);//釋放Lb的頭結點
}//MergeList_L                

單鏈表結構的優缺點

優點

  • 動態結構,整個儲存空間為多個連結串列共用
  • 不需預先分配空間

缺點

  • 指標佔用額外儲存空間
  • 不能隨機存取,查詢速度慢

迴圈連結串列

  • 在單鏈表中,將終端結點的指標域NULL改為指向表頭結點的或開始結點,就得到了單鏈形式的迴圈連結串列,並簡單稱為單迴圈連結串列。
  • 為了使空表和非空表的處理一致,迴圈連結串列中也可設定一個頭結點。
  • 由於迴圈連結串列中沒有NULL指標,故涉及遍歷操作時,其終止條件就不再像非迴圈連結串列那樣判斷p或p—>next是否為空,而是判斷它們是否等於頭指標

雙向連結串列

結構定義

typedef struct DulNode {
     ElemType   data;
     struct DuLNode *prior;
     struct DuLNode *next;
}DuLNode,  *DuLinkList;

插入結點程式

Status ListInsert_DuL(DuLinklist L, int i, ElemType e)
{ 
   DuLinklist s,p;
   if (!(p=GetElemP_DuL(L,i))) 
      return ERROR;      // 在L中確定第i個元素的位置指標p
   if(!(s = (DuLinklist)malloc(sizeof(DuLNode)))) return ERROR;
   s->data = e;                // 構造資料為e的結點s
   s->prior = p->prior;  p-> prior ->next = s;
   s->next = p;        p->prior = s;
   return OK;
}  // ListInsert_DuL

刪除結點程式

Status ListDelete_DuL(DuLinklist L, int i, ElemType &e)
{
   DuLinklist p;
   if (!(p=GetElemP_DuL(L,i))) 
        return ERROR;    // 在L中確定第i個元素的位置指標p
   e = p->data;                // 刪除的結點的值存入e
   p-> prior ->next = p->next;
   p->next->prior = p->prior;
   free(p);
   return OK;
}  // ListDelete_DuL

帶頭結點的線性連結串列型別

具有實用意義的線性連結串列

 typedef struct LNode // 結點型別
 {
      ElemType data;
      LNode *next;
 }*Link,*Position;

 struct LinkList // 連結串列型別
 {
      Link head,tail; // 分別指向線性連結串列中的頭結點和最後一個結點
      int len;      // 指示線性連結串列中資料元素的個數
 };