【數據結構知識點總結】二、線性表

線性表是具有相同數據類型的n(n≥0)個數據元素的有限序列,n爲表長,當n=0時該線性表是一個空表。
邏輯特性:除表頭元素外,每個元素有且僅有一個直接前驅;除最後一個元素外,每個元素有且僅有一個直接後繼。
線性表基本操作的實現取決於採用哪種存儲結構。

線性表的順序表示

線性表的順序存儲又稱順序表。
線性表中元素的位序是從1開始的,而數組中元素的下標是從0開始的。

線性表的數據類型描述:

#define MAXSIZE N //定義線性表的最大長度
typedef struct SqList{
	ElemType data[MAXSIZE];
	int length;
}SqList;

數組可以是靜態分配的,也可以是動態分配的。
動態分配時不需要爲線性表一次性地劃分所有空間

#define InitSize 100   //表長度的初始定義
typedef struct SqList{
	ElemType *data;
	int MaxSize,length;
}SqList;

SqList L;
//c初始動態分配
L.data=(ElemType*)malloc(sizeof(ElemType)*InitSize);
//c++
L.data=new ElemType[InitSize];

順序表最主要的特點是隨機訪問,即通過首地址和元素序號可在時間O(1)O(1)內找到指定元素。
順序表的存儲密度高,每個結點只存儲數據元素。
順序表邏輯上相鄰的元素在物理上也相鄰,插入和刪除操作需要移動大量元素。

(1)插入

//在順序表的第i個位置插入e
bool ListInsert(SqList &L,int i,ElemType e){
	if(i<1||i>L.length+1){ //插入位置無效
		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;
}

*線性表元素位序從1開始,數組從0開始
平均時間複雜度O(n)O(n)

(2)刪除

//刪除順序表L第i個位置的元素
//若成功返回true,並將被刪除的元素引用變量e返回
bool ListDelete(SqList &L,int i,ElemType &e){
	if(i<1||i>L.length){
		return false;
	}
	e=L.data[i-1];
	for(int j=i;j<L.length;j++){
		L.data[i-1]=L.data[i];
	}
	L.length--;
	return true;
}

平均時間複雜度爲O(n)O(n)

(3)按值查找

//查找表中第一個值爲e的元素,並返回它在順序表中的位置
int LocateElem(SqList &L,ElemType e){
	int i;
	for(i=0;i<L.length;i++){
		if(L.data[i]==e){
			return i+1;
		}
	}
	return 0;
}
線性表的鏈式表示

鏈式存儲線性表時,不需要使用地址連續的存儲單元,即它不要求邏輯上相鄰的兩個元素在物理位置上也相鄰。

線性表的鏈式存儲又稱單鏈表。對每個鏈表結點,除存放元素自身的信息外,還需要存放一個指向其後繼的指針。

typeof struct LNode{
	ElemType data;
	struct LNode *next;
}LNode,*LinkList;

由於單鏈表的元素是離散地分佈在存儲空間中的,所以單鏈表是非隨機存取的存儲結構,即不能直接找到表中某個特定的結點。
通常用頭指針來標識一個單鏈表,頭指針爲NULL時表示一個空表。爲操作方便,在單鏈表第一個結點之前附加一個結點,稱爲頭結點。頭結點的數據域可以不設任何信息,也可以記錄表長等相關信息。頭結點的指針域指向線性表的第一個元素結點。
不管帶不帶頭結點,頭指針始終指向鏈表的第一個結點,而頭結點是帶頭結點的鏈表中的第一個結點,結點內通常不存儲信息。

(1)尾插法建立單鏈表

//從表頭到表尾正向建立單鏈表L
LinkList List_TailInsert(LinkList &L){
	ElemType x;
    L=(LinkList)malloc(sizeof(LNode));
    LNode *s,*r=L; //r爲表尾指針
    cin>>x;
    while(x!=-1){ //輸入-1表示結束
        s=(LNode *)malloc(sizeof(LNode));
        s->data=x;
        r->next=s;
        r=s; //r指向新的表尾結點
        cin>>x;
    }
    r->next=NULL; //尾結點指針置空
    return L;
}

時間複雜度O(n)O(n)
在這裏插入圖片描述

(2)按序號查找結點值

//本算法取出單鏈表L(帶頭結點)中第i個位置的結點指針
LNode *GetElem(LinkList L,int i){
    int j=1;
    LNode *p=L->next; //頭結點指針賦給p
    if(i==0){
        return L; //返回頭結點
    }
    if(i<0){ //i無效
        return NULL;
    }
    while(p&&j<i){
        p=p->next;
        j++;
    }
    return p; //返回第i個結點的指針,若i大於表長,p=NULL,直接返回p即可
}

時間複雜度O(n)O(n)

(3)按值查找表結點

LNode *LocalElem(LinkList L,ElemType e){
    LNode *p=L->next;
    while(p!=NULL&&p->data!=e){
        p=p->next;
    }
    return p; //找到後返回該結點指針,否則返回NULL
}

時間複雜度O(n)O(n)

(4)插入結點
先檢查插入位置的合法性,然後找到待插入位置的前驅結點,即第i-1個結點,再在其後插入新結點。

p=GetElem(L,i-1);
s->next=p->next;
p->naxt=s;

在這裏插入圖片描述
算法的時間開銷主要在查找第i-1個元素,時間複雜度爲O(n)O(n),若在給定的結點後面插入新結點,時間複雜度僅爲O(1)O(1)

(5)刪除結點
先檢查刪除位置的合法性,然後查找表中第i-1個結點,再將第i個結點刪除
在這裏插入圖片描述

p=GetElem(L,i-1);
q=p->next;
p->next=q->next;
free(q);

算法的時間開銷主要爲查找,時間複雜度爲O(n)O(n)

若要刪除結點*p,可以通過將其後繼結點的值賦予自身,然後刪除其後繼結點來實現,使得時間複雜度爲O(1)O(1)

q=p->next;
p->data=p->next->data;
p->next=q->next;
free(q);

(6)求表長
求表長即計算單鏈表中數據結點(不含頭結點)的個數。時間複雜度爲O(n)O(n)

雙鏈表
雙鏈表結點中有兩個指針prior和next,分別指向前驅結點和後繼結點

typeof sturct DNode{
	ElemType data;
	struct DNode *prior,*next;
}DNode,*DLinkList;

雙鏈表“鏈”變化時也需要對prior指針做出修改

(1)插入:

s->next=p->next;
p->next->prior=s;
s->prior=p;
p->next=s;

(2)刪除:

p->next=q->next;
q->next->prior=p;
free(q);

循環鏈表

1.循環單鏈表
  在循環單鏈表中,表尾結點*r的next域指向L,故表中沒有指針域爲NULL的結點,因此循環單鏈表的判空條件不是頭結點的指針是否爲空,而是它是否等於頭指針。
在這裏插入圖片描述
2.循環雙鏈表
在這裏插入圖片描述
  爲空時,頭結點的prior域和next域都等於L

靜態鏈表
  靜態鏈表藉助數組來描述線性表的鏈式存儲結構,這裏的指針是結點的相對地址(數組下標),也稱遊標。靜態鏈表也要預先分配一塊連續的內存空間。

在這裏插入圖片描述

#define MaxSize 50 //靜態鏈表的最大長度
typeof struct SLinkList{
    ElemType data;
    int next; //下一個元素的數組下標
}SLinkList[MaxSize];

  靜態鏈表以next==-1作爲結束的標誌。靜態鏈表的插入、刪除操作與動態鏈表相同,只需要修改指針,不需要移動元素。

順序表和鏈表的比較
  1. 存取方式
    順序表可以順序存放,也可以隨機存放。鏈表只能順序存取元素。

  2. 邏輯結構與物理結構
    順序表邏輯結構相鄰的元素物理位置也相鄰。鏈式存儲邏輯上相鄰的元素物理位置不一定相鄰。

  3. 查找、插入和刪除操作
    按值查找,順序表無序時,時間複雜度爲O(n)O(n),順序表有序時,可採用折半查找,此時時間複雜度爲O(log2n)O(log_2n)。鏈表時間複雜度O(n)O(n)
    按序號查找,順序表時間複雜度O(1)O(1),鏈表的時間複雜度O(n)O(n)
    順序表插入、刪除操作,平均需要移動半個表長的元素。鏈表只需要修改相關結點的指針域。

  4. 空間分配
    順序存儲在靜態存儲分配情形下,一旦存儲空間裝滿就不能擴充,若再加入新元素,則會出現內存溢出,因此需要預先分配足夠大的存儲空間,預先分配過大,有可能會導致順序表後部大量閒置。動態存儲分配雖然存儲空間可以擴充,但需要移動大量元素,導致操作效率降低,若內存中沒有更大塊的連續存儲空間,則會導致分配失敗。
    鏈式存儲的結點空間只在需要時申請分配,只要內存有空間就可以分配,操作靈活、高效。

練習

在這裏插入圖片描述
(1)可將問題視爲將數組abab轉換爲數組baba
  先將aabb分別逆置得到a1b1a^{-1}b^{-1},再將整個a1b1a^{-1}b^{-1}逆置得到(a1b1)1=ba(a^{-1}b^{-1})^{-1}=ba
(2)

void Reverse(int R[],int from,int to){
	int i,temp;
    for(i=0;i<(to-from+1)/2;i++){
        temp=R[from+i];
        R[from+i]=R[to-i];
        R[to-i]=temp;
    }
}
void Converse(int R[],int n,int p){
    Reverse(R,0,p-1);
    Reverse(R,p,n-1);
    Reverse(R,0,n-1);
}

(3)時間複雜度O(n)O(n),空間複雜度O(1)O(1)


在這裏插入圖片描述
(1)分別求A、B的中位數,設爲a、b
①若a=b,則a/b即爲所求中位數
②若a<b,則捨棄序列A中較小的一半和序列B中較大的一半,要求兩次捨棄的長度相等
③若a>b,則捨棄A中較大的一半和B中較小的一半,要求兩次捨棄的長度相等。
在保留的序列中,重複①②③,直到兩個序列均只含一個元素爲止,較小者即爲中位數
(2)

int MSearch(int A[],int B[],int n){
    int s1=0,d1=n-1,s2=0,d2=n-1;
    int m1,m2;
    while(s1!=d1||s2!=d2){
        m1=(s1+d1)/2;
        m2=(s2+s2)/2;
        if(A[m1]==B[m2]){
            return A[m1];
        }
        if(A[m1]<B[m2]){
            if((s1+d1)%2==0){ //若元素個數爲偶數
                s1=m1;
                d2=m2;
            }
            else{
                s1=m1+1;
                d2=m2;
            }
        }
        else{
            if((s2+d2)%2==0){
                d1=m1;
                s2=m2;
            }
            else{
                d1=m1;
                s2=m2+1;
            }
        }
    }
    return A[s1]<B[s2]?A[s1]:B[s2];
}

(3)時間複雜度O(log2n)O(log_2n),空間複雜度O(1)O(1)


在這裏插入圖片描述
(1)從前向後掃描數組元素,標記出一個可能成爲主元素的元素。最後重新計數,確認該元素是否爲主元素。
①選取數組第一個元素作爲主元素記爲c,依次掃描所給數組中的每個整數,記錄主元素出現的次數,若遇到的下一個整數仍等於c,則計數加1,否則計數減1;當技術減到0時,將遇到的下一個整數保存到c中,計數重置爲1,直到掃描完全部數組元素。
②判斷c中元素是否是真正的主元素。再次掃描該數組,統計c出現的次數,若大於n/2,則爲主元素,否則序列中不存在主元素。
(2)

int Major(int A[],int n){
    int c,count=1;
    c=A[0];
    for(int i=0;i<n;i++){
        if(A[i]==c){
            count++;
        }
        else{
            if(count>0){
                count--;
            }
            else{
                c=A[i];
                count=1;
            }
        }
    }
    if(count>0){
        for(i=count=0;i<n;i++){
            if(A[i]=c){
                count++;
            }
        }
    }
    if(count>n/2){
        return c;
    }
    else{
        return -1;
    }
}

(3)時間複雜度O(n)O(n),空間複雜度O(1)O(1)


在這裏插入圖片描述
(1)空間換時間。分配一個用於標記的數組B[n],用來記錄A中是否出現1~n中的數,B[0]對應1,B[n-1]對應n,初始化B中均爲0。
當A中n個數恰好爲1~n時返回n+1。當數組A中出現小於等於0或者大於n的值時,1~n中必會出現空餘位置。
從A[0]開始遍歷A,若0<A[i]<=n,則令B[A[i]-1]=1。遍歷結束後遍歷數組B,若找到滿足B[i]==0的下標i,循環結束,返回結果i+1;若B[i]均不爲0,循環結束後返回結果i+1。
(2)

int findMissMin(int A[],int n){
    int *B;
    B=(int *)malloc(sizeof(int)*n); //分配大小爲n的空間
    memset(B,0,sizeof(int)*n);
    for(int i=0;i<n;i++){
        if(A[i>0]&&A[i]<=n){
            B[A[i]-1]=1;
        }
    }
    for(int i=0;i<n;i++){
        if(B[i]==0){
            break;
        }
    }
    return i+1;
}

(3)時間複雜度O(n)O(n),空間複雜度O(n)O(n)


在這裏插入圖片描述
將頭結點摘下,然後從第一個結點開始,依次插入到頭結點的後邊(頭插法建立單鏈表)

LinkList Reserve(LinkList L){
    LNode *p,*r; //r爲p的後繼
    p=L->next;
    L->next=NULL;
    while(p!=NULL){
        r=p->next;
        p->next=L->next;
        L->next=p;
        p=r;
    }
    return L;
}

時間複雜度O(n)O(n),空間複雜度O(1)O(1)


在這裏插入圖片描述
(1)定義兩個指針變量p和q,初始時均指向頭結點的下一個結點,p指針沿鏈表移動;當p指針移動到第k個結點時,q指針開始與p指針同步移動;當p移動到最後一個結點時,q指針所指結點即爲倒數第k個結點。
(3)

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

int Search_k(LinkList list,int k){
    LNode *p=list->next,*q=list->next;
    int count=0;
    while(p!=NULL){
        if(count<k){
            count++;
        }
        else{
            q=q->next;
        }
        p=p->next;
    }
    if(count<k){
        return 0;
    }
    else{
        printf("%d",q->data);
        return 1;
    }
}

時間複雜度O(n)O(n),空間複雜度O(1)O(1)


在這裏插入圖片描述
(1)①分別求出str1和str2所指兩個鏈表的長度m和n
   ②令p、q分別指向str1和str2的頭結點,若m≥n,則指針p先走,使p指向第m-n+1個結點,若m<n,則先使q指向第n-m+1個結點。
   ③反覆將p和q同步向後移動,當p、q指向同一位置時停止,即爲共同後綴的起始位置。
(2)

typedef struct Node{
    char data;
    struct Node *next;
}Node;

//求鏈表長度
int ListLen(Node *head){
    int len=0;
    while(head->next!=NULL){
        len++;
        head=head->next;
    }
    return len;
}

//找到共同後綴的起始地址
Node* findAddr(Node *str1,Node *str2){
    int m,n;
    Node *p,*q;
    m=ListLen(str1);
    n=ListLen(str2);
    for(p=str1;m>n;m--){
        p=p->next;
    }
    for(q=str2;m<n;n--){
        q=q->next;
    }
    while(p->next!=NULL&&p->next!=q->next){
        p=p->next;
        q=q->next;
    }
    return p->next;
}

(3)時間複雜度O(max(len1,len2))O(max(len1,len2)),空間複雜度O(1)O(1)


在這裏插入圖片描述
在這裏插入圖片描述
(1)使用輔助數組記錄鏈表中已出現的數值。因爲|data|≤n,所以輔助數組的大小爲n+1,初值爲0。
依次掃描鏈表中各結點,同時檢查q[|data|]的值,若爲0則保留該結點,並令q[|data|]=1;否則將該結點刪除。
(2)

typedef struct Node{
	int data;
	struct Node *link;
}Node,*PNode;

(3)

void func(PNode h,int n){
	PNode p=h,r;
	int *q,m;
	q=(int *)malloc(sizeof(int)*(n+1)); //申請n+1個位置的輔助空間
	for(int i=0;i<n+1;i++){
		*(q+1)=0;
	}
	while(p->link!=NULL){
		m=p->link->data>0?p->link->data:-p->link->data;
		if(*(q+m)==0){
			*(q+m)=1;
			p=p->link;
		}
		else{
			r=p->link;
			p->link=r->link;
			free(r);
		}
	}
	free(q);
}

(4)時間複雜度O(m)O(m),空間複雜度O(n)O(n)

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