1. 程式人生 > >線性表及其應用C語言實現(資料結構複習最全筆記)

線性表及其應用C語言實現(資料結構複習最全筆記)

一、順序表的表示與實現

1.線性表的順序結構定義

#define LIST_INIT_SIZE  100 //線性表儲存空間的初始分配量
#define LISTINCREMENT   10 //線性表儲存空間的分配增量
typedef struct
{
    ElemType* elem;   //儲存空間基地址
    int length;       //表中元素的個數(表長)
    int listsize;     //表容量大小(以sizeof(ElemType)為單位)
} SqList;   //順序表型別定義

2.順序表的初始化(構建空的線性表)

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

3.順序表的插入操作

Status ListInsert_Sq(SqList &L, int pos, ElemType e)
{
    ElemType *newbase;
    if(pos>=1&&pos<=L.length+1)//注意這個範圍
    {
        if(L.length>=L.listsize)//此時要擴容
        {
            newbase = (ElemType*)realloc(L.elem,(L.listsize+LISTINCREMENT)*sizeof(ElemType));
            if(!newbase)
                return ERROR;
            L.elem = newbase;//新分配空間的基地址
            L.listsize+=LISTINCREMENT;
        }
        //即將進行插入操作
        ElemType *p,*q;
        p = &(L.elem[pos-1]);//將原來pos位置元素的地址分配給指標p,即p為插入位置
        for(q = &(L.elem[L.length-1]);q>=p;--q)
            *(q+1) = *q;//將原來順序表最後一個位置資料的地址分配給q,然後從後往前依次將資料向後移動一位
        *p = e;
        ++L.length;
        return OK;
    }
    else
        return OVERFLOW;
}

4.順序表的刪除操作


Status ListDelete_Sq(SqList &L, int pos, ElemType &e)
{
    if(pos>=1&&pos<=L.length)//注意範圍
    {
        ElemType *p;
        e = L.elem[pos-1];//先將e賦值,也就是返回刪掉了哪個數,因為要返回引用
        for(p = &(L.elem[pos-1]);p<=&(L.elem[L.length-1]);p++)
            *p = *(p+1);
        --L.length;
        return OK;
    }
    else
        return OVERFLOW;
}

5.順序表的查詢操作

int ListLocate_Sq(SqList L, ElemType e)
{
    int a = -1;//給a賦初值,無論查詢的資料在第幾個,都不可能是第-1個,所以賦值-1
    for(int i = 0;i<=L.length-1;i++)
    {
        if(L.elem[i] == e)
        {
            a = i;
            break;
        }
    }
    if(a>=0&&a<=L.length-1)
        return a+1;
    else
        return ERROR;
}

6.順序表常見的輸出函式

void ListPrint_Sq(SqList L)
{
    ElemType *p = L.elem;//遍歷元素用的指標
    for(int i = 0; i < L.length; ++i){
        if(i == L.length - 1){
            printf("%d", *(p+i));
        }
        else{
            printf("%d ", *(p+i));
        }
    }
}

7.順序表的就地逆置

void ListReverse_Sq(SqList &L)
{
    int tmp;
    //int n;
    for(int i = 0;i< n/2;i++)
    {
        tmp = L.elem[i];
        L.elem[i]=L.elem[n-i-1];
        L.elem[n-i-1] = tmp;
    }
}

8.順序表的合併

 <1>順序表的無序合併

SqList bingji(SqList &S1,SqList &S2)
{
    SqList S;
    SqListInit(S);
    int i,j;
    for(i = 0;i<S1.length;i++)
    {
        S.elem[i] = S1.elem[i];
        S.length++;
    }
    for(j=0;j<S2.length;j++)
    {
        int flag = 1;
        for(int k = 0;k<S.length;k++)
        {
            if(S2.elem[j] == S.elem[k])
            {
                flag = 0;
                break;
            }
        }
        if(flag)
        {
            S.elem[i++] = S2.elem[j];
            S.length++;
        }
    }
    return S;

}

<2>順序表的有序合併(升序)

void MergeList_sq(SqList La,SqList Lb,SqList &Lc)
{
    //實現程式碼
    pa = La.elem;pb = Lb.elem;
    Lc.listsize = Lc.length = La.length+Lb.length;
    pc = Lc.elem = (ElemType*)malloc(Lc.listsize*sizeof(ElemType));
    if(!Lc.elem)
        exit(OVERFLOW);
    pa_last = La.elem + La.length - 1;
    pb_last = Lb.elem + Lb.length - 1;
    while(pa<=pa_last&&pb<=pb_last)
    {
        if(*pa<=*pb)
            *pc++ = *pa++;
        else
            *pc++ = *pb++;
    }
    while(pa<=pa_last)
        *pc++ = *pa++;
    while(pb<=pb_last)
        *pc++ = *pb++;
    /*
    //演算法大致程式碼
    InitList(Lc);
    i = j =1;k = 0;
    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)
{
    GetElem(La,i,ai);
    ListInsert(Lc,++k,ai);
}
while(j<=Lb_len)
{
    GetElem(Lb,j,bj);
    ListInsert(Lc,++k,bj);
}
    */
}

<2>(單)連結串列的表示與實現

1.單鏈表的鏈式結構定義

typedef struct LNode
{  
    ElemType data;  //資料域
    struct LNode *next; //指標域
}LNode,*LinkList; //迴圈單鏈表型別定義與單鏈表定義相同,區別在尾節點next取值

2.單鏈表的讀取操作(獲取第i個元素位置)

Status GetElem(LinkList &L,int i,ElemType &e)
{
    LinkList p = L->next;//p指向第一個結點
    int j = 1;//計數器j
    while(p&&j<i)
    {
        p = p->next;//就是遍歷,往後挪
        j++;
    }
    if(j>i||!p)
        return 0;
    e = p->data;
    return OK;
}

3.單鏈表的插入操作

Status LinkListInsert(LinkList &L,int pos,ElemType e)
{
    LinkList p = L->next,s;
    int j = 1;
    while(p&&j<pos-1)
    {
        p = p->next;
        j++;
    }
    if(!p&&j>pos-1)
        return 0;
    s = (LinkList)malloc(sizeof(LNode));
    s->data = e;
    s->next =p->next;//這兩部是插入操作的核心
    p->next =s;
    return 1;
}

4.單鏈表的刪除操作

Status ListDelete(LinkList &L,int i,ElemType &e)
{
    LinkList p = L->next,q;
    int j = 1;
    while(p&&j<i-1)
    {
        p = p->next;
        j++;
    }
    if(!p||j>i-1)
        return 0;
    q = p->next;//這兩步是刪除操作的核心
    p->next = q->next;
    e = q->data;
    free(q);//別忘了free
    return 1;
}

5.單鏈表的求表長操作

Status ListLength(LinkList &L)
{
    LinkList p = L->next;
    int j = 0;
    while(p)
    {
        p = p->next;
        j++;
    }
    return j;
}

6.單鏈表常用列印操作

void ListPrint(LinkList &L)
{
    LinkList p = L->next;//p指向第一個元素結點
    int flag = 1;
    while(p!=NULL)
    {
        if(flag)
        {
            cout<<p->data;
            flag = 0;
        }
        else
        {
            cout<<" "<<p->data;
        }
        p = p->next;
    }
    cout<<endl;
}

7.單鏈表的判空操作

Status JudgeEmpty(LinkList &L)
{
    LinkList p = L->next;
    if(p)
        return false;
    else
        return true;
}

8.單鏈表的元素定位操作

Status ListLocate(LinkList &L,ElemType e)
{
    LinkList p = L->next;
    int j = 1;
    while(p&&e!=p->data)
    {
        p = p->next;
        j++;
    }
    if(!p)
        return 0;
    else
        return j;
}

8.單鏈表常用free函式

void ListFree(LinkList &L)//最好自己寫free函式
{
    LinkList p = L->next;
    //LinkList rear;
    //rear = L;
    while(p)
    {
        free(p);
        //rear = p;
        p = p->next;
    }
    free(p);
}

9.單鏈表的整表建立

a.頭插法

Status ListCreate_CL(LinkList &CL,int n)
{
    LinkList p;
    int i;
    L = (LinkList)malloc(sizeof(LNode));
    L->next = NULL;
    for(int i = 0;i<n;i++)
    {
        p = (LinkList)malloc(sizeof(LNode));
        scanf("%d",&p->data);
        p->next = L->next;
        L->next = p;//插入到表頭
    }
}

 

b.尾插法(正常思路,最常見)(兩種)

Status ListCreate_L(LinkList &L,int n)
{
    LinkList rear,p;   //一個尾指標,一個指向新節點的指標
    L=(LinkList)malloc(sizeof (LNode));
    if(!L)exit(OVERFLOW);
    L->next=NULL;               //先建立一個帶頭結點的單鏈表
    rear=L;  //初始時頭結點為尾節點,rear指向尾巴節點
    for (int i=1;i<=n;i++){  //每次迴圈都開闢一個新節點,並把新節點拼到尾節點後
        p=(LinkList)malloc(sizeof(LNode));//生成新結點
        if(!p)exit(OVERFLOW);
        scanf("%d",&p->data);//輸入元素值
        p->next=NULL;  //最後一個節點的next賦空
        rear->next=p;
        rear=p;
    }
    return OK;
}

//或者

Status ListCreate_CL(LinkList &CL)
{
    CL = (LinkList)malloc(sizeof(LNode));
    //LinkList head = CL;
    LinkList rear = CL;
    rear->next = NULL;
    int t;
    while(cin>>t&&t!=-1)
    {
        LinkList p = (LinkList)malloc(sizeof(LNode));
        p->data = t;
        rear->next = p;//把p插入尾部,指標移到最後
        rear = p;//數值移到最後
    }
    rear->next = NULL;//讓最後的尾指標指向NULL,否則連結串列建立無法停止
    return 0;
}

10.單鏈表的整表刪除

演算法思路:

宣告兩個結點p和q

將第一個結點賦值給p

迴圈:

將下一個結點賦值給q

釋放p

將q賦值給p

Status ClearList(LinkList &L)
{
    LinkList p = L->next,q;
    while(p)
    {
        q = p->next;
        free(p);
        q = p;
    }
    L->next = NULL;
    return OK;
}

11.單鏈表的就地逆置(較難)

演算法思想:逆置連結串列初始為空,表中節點從原連結串列中依次“刪除”,再逐個插入逆置連結串列的表頭(即“頭插”到逆置連結串列中),使它成為逆置連結串列的“新”的第一個結點,如此迴圈,直至原連結串列為空。

void ListReverse_L(LinkList &L)//L為頭結點
{
    LinkList p,q;
    p = L->next;
    L->next = NULL;
    while(p)
    {
        //向後挪
        q = p;//
        p = p->next;
        //頭插
        q->next = L->next;//非常重要,相當於p和q之間沒有了指標連線
        L->next = q;//把q接到頭的後面
    }
}


詳細圖解參考一位大佬的文章:https://blog.csdn.net/v_xchen_v/article/details/53067448

12.單鏈表的合併

void merge(LinkList La,LinkList Lb,LinkList &Lc){
    LinkList pa,pb,pc;
    pa=La->next;pb=Lb->next;
    Lc=pc=La;
    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);
}

 13.單鏈表的遞迴建立

Status ListCreate_L_Rec(LinkList &L,int n)
{
//遞迴邊界:建立空表時只需將L賦空即可;
//遞迴關係:建立非空表時,將連結串列看做兩部分:首元素組成的子表La, 第二個元素及其後元素構成的子表Lb。
//子表La容易建立(只需開闢一個節點)
//子表Lb由於規模小可以遞迴建立完成(類似數學歸納法的假設,只要小的都可以建設能完成)
//最後將兩個子表拼接即可。
  LNode *La, *Lb;
  if(n==0) L=NULL;
  else{
    La=(LNode *)malloc(sizeof(LNode));  //開闢子表La
    if(!La) exit(OVERFLOW);
    scanf("%d",&La->data);
    ListCreate_L_Rec(Lb,n-1 ); //遞迴建立子表Lb
    La->next = Lb;  //兩個子表拼結
    L=La;  //第一個子表的地址賦給L。請思考為什這樣處理不會使得主函式中的L取錯值?
  }
  return OK;
}

14.單鏈表的遞迴輸出

方法1:遞迴法 

 

void ListPrint_L_Rec(LinkList L)
{
    if(!L)
        return;
    printf(" %d",L->data);
        ListPrint_L_Rec(L->next);
        return;
}

方法2:直接輸出法

void ListPrint_L_Rec(LinkList L)
{
    LinkList p = L;
    //int flag = 1;
    while(p)
    {
        //int flag = 1;
            printf(" %d",p->data);
        p = p->next;
    }
    printf("\n");
}

 

之後還會更新靜態連結串列,迴圈連結串列,雙向連結串列的筆記。

如果哪裡程式碼有問題歡迎指正