1. 程式人生 > >帶頭結點的線性連結串列的基本操作

帶頭結點的線性連結串列的基本操作

持續了好久,終於有了這篇部落格,連結串列的操作需要藉助影象模型進行反覆學習,這裡儘可能的整理並記錄下自己的思考,以備後面複習,和大家分享。需要說明的是,我們從實際應用角度出發重新定義了線性表。

一. 定義

從上一篇文章可以看到,由於連結串列在空間的合理利用上和插入、刪除時不需要移動等優點,因此在很多場合下,它是線性表的首選儲存結構。然而,它也存在某些實現的缺點,如求線性表的長度時不如順序儲存結構的缺點。因此,從實際應用角度出發重新定義了線性表。
一個帶頭結點的線性連結串列型別定義如下:

//結點型別
typedef struct Node
{
    ElemType   data;
    struct
Node *next; }Node,*PNode; //連結串列型別 typedef struct List { PNode head; //指向線性連結串列的頭結點 PNode tail; //指向線性連結串列的尾結點 size_t size; //線性表中資料元素的個數 }List;

二、函式實現的難點

我們基於一個改良的連結串列型別,進行常見的基本操作,這裡將就每個函式實現的重難點找出來,使其核心精髓展現在我們眼前:

InitList(List *list)

該函式實現了將我們定義的連結串列進行初始化,使其頭指標、尾指標都指向頭結點(空表),連結串列的大小size為0.
這裡寫圖片描述

void push_back(List *list,ElemType x)

尾插入,首先構建一個節點,然後將其插入連結串列尾部。此時體現出來該種資料結構的優勢,我們無需從連結串列首部直接操作,直接找到了連結串列尾部對其操作。

void push_front(List *list,ElemType x)

頭插,核心是用於在頭結點和第一個結點之間插入新的子節點。當然,如果插入前為空表,需修改連結串列的尾結點指向第一個結點。(因為是對頭部操作,我們需要留意尾結點)

void pop_back(List *list)

尾部刪除,首先判斷連結串列是否非空,然後不停的刪除尾結點。當然,需要找到尾結點的前驅,我們需要順序遍歷。

void pop_front(List *list)

頭部刪除,首先判斷連結串列是否非空,然後不停的刪除頭結點第一個結點。需要注意,如果刪除到只剩頭結點時,需要將尾指標指向頭結點。(因為是對頭部操作,我們需要留意尾結點)

void insert_val(List *list, ElemType x)

插入值,也即插入一個結點。前提是有序,按值插入(需考慮插入到尾結點之後情況:此時更新尾結點指標)。

Node* find (List *list, ElemType key)
void delete_val(List *list,ElemType key)

刪除值,呼叫了find方法,我們首先找到指向當前要刪除值的結點,然後將下一結點的值賦值給當前結點,使當前結點指向下一結點的next,刪除下一結點,從而變相的刪除了當前結點。
當然,我們也可以通過順序遍歷,找到當前要刪除值結點的前驅,然後刪除該節點。

void sort(List *list)

本篇文章的重點:排序。之前的實現中,我們都是通過某種排序方法,交換結點中的data,從而實現。本篇文章我們通過交換結點的方法來實現。
這裡寫圖片描述
首先,我們把整個單鏈表斷開,已排序部分僅僅包括頭結點和第一個結點(尾指標指向第一個結點),待排序部分從第二個結點開始餘下的部分。
我們每次從待排序部分取出一個結點s,將其值和已排序部分順序比較,從而找到要插入位置的前驅p,插入前要更新q為待排序連結串列的第一個結點。然後尾插即可。

void reverse(List *list)

本篇文章的重點:反轉。同上,我們還是把整個單鏈表斷開分為兩部分:已排序部分僅僅包括頭結點和第一個結點(尾指標指向第一個結點),待排序部分從第二個結點開始餘下的部分。
我們每次從待排序部分取出一個結點給p,然後在頭結點和第一個結點之間頭插實現。

三、程式碼

sList.h

#ifndef SLIST_H__
#define SLIST_H__

#include <stdio.h>
#include <malloc.h>
#include <assert.h>

typedef int ElemType;

//結點型別
typedef struct Node
{
    ElemType   data;
    struct Node *next;
}Node,*PNode;

//連結串列型別
typedef struct List
{
    PNode head;
    PNode tail;
    size_t size;
}List;


void InitList(List *list);

void push_back(List *list,ElemType x); //尾插
void push_front(List *list,ElemType x); //頭插

void pop_back(List *list); //尾刪
void pop_front(List *list); //頭刪

void insert_val(List *list, ElemType x); //按值插入
Node* find (List *list, ElemType key);//查詢某值
int length(List *list); //長度
void delete_val(List *list,ElemType key); //刪除值
void sort(List *list);//升序
void reverse(List *list);//反轉
void clear(List *list);//清除
void destroy(List *list);//銷燬
void show_list(List *list);
#endif // SLIST_H__

sList.cpp

#include "sList.h"


void InitList(List *list)
{
    list->head = list->tail = (Node *)malloc(sizeof(Node));
    assert(list->head != NULL);

    list->head->next = NULL; //頭結點下一個為空
    list->size = 0;
}

//1.更新新尾結點(一直對尾結點操作,故不需考慮)
void push_back(List *list,ElemType x)
{

    Node *s = (Node *)malloc(sizeof(Node));
    assert (s != NULL);

    s->data = x;

    s->next = list->tail->next;
    list->tail->next = s; //原尾結點指向s

    list->tail = s;//更新為新尾結點

    //由於一直對尾指標考慮,因此這裡不需要考慮size=0

    list->size++;
}

//1.在頭結點之後插入作為第一個結點(需要考慮尾結點)
void push_front(List *list,ElemType x)
{
    Node *s = (Node *)malloc(sizeof(Node));
    assert (s != NULL);

    s->data = x;
    s->next = list->head->next; //s結點指向第一個結點

    list->head->next = s; //頭結點指向第一個結點

    //插入時,如果是第一個結點(size=0)則需修改list->tail指標指向s
    if(list->size == 0)
    {
        list->tail = s;
    }
    list->size++;
}
//1.判斷非空 2.使倒數第二個結點為新尾結點(一直對尾結點操作,故不需考慮)
void pop_back(List *list)
{
    if(list->size == 0)
    {
        printf("error: seqList is empty\n");
        return;
    }

    Node *p = list->head;


    while (p->next != list->tail) { p = p->next; }

    free(list->tail);//刪除舊尾結點

    list->tail = p;//新尾結點
    list->tail->next = NULL;//新尾結點next為NULL


    list->size--;
}

//1.判斷非空 2.刪除頭結點之後第一個結點 3.需要考慮尾結點
void pop_front(List *list)
{
    if(list->size == 0)
    {
        printf("error: seqList is empty\n");
        return;
    }

    Node *q = list->head->next;
    list->head->next = q->next;
    free(q);
    //刪除時,如果只剩第一個結點(size=1)則需修改list->tail指標指向頭結點
    if(list->size == 1)
    {
        list->tail = list->head;
    }
    list->size--;
}
//前提:有序。按值插入(考慮插入到尾結點之後情況)
void insert_val(List *list, ElemType x)
{
   Node *s = (Node *)malloc(sizeof(Node));
   assert(s != NULL);

   s->data = x;
   s->next = NULL;

   Node *p = list->head;
   while(p->next != NULL && p->next->data <x)
        p=p->next;

   //若插入到尾結點,則更新尾結點
   if(p->next == NULL)
   {
        list->tail = s;
   }
   //找到該插入位置的前一位
   s->next = p->next;
   p->next = s; //指向新建立的結點

   list->size++;

}

Node* find (List *list, ElemType key)
{
    Node *p = list->head->next;
    //利用短路條件,注意順序
    while( p!=NULL && p->data != key)  { p = p->next; }

    return p;
}

int length(List *list)
{
    return list->size;
}

void delete_val(List *list,ElemType key)
{
    if(list->size == 0)
        return;
    Node *p = find(list,key);
    if(p == NULL)
    {
        printf("delete val not exist\n");
        return ;
    }
    if(p == list->tail)
    {
        pop_back(list);
    }else
    {
        //將要刪除資料的next拿過來替換值,取出next的有用資訊後free掉q
        Node *q = p->next;
        p->data = q->data;
        p->next = q->next;
        free(q);

        list->size--;
    }

}

//把整個單鏈表斷開,把剩下連結串列的結點根據值升序尾插
void sort(List *list)
{
    if(list->size==0 || list->size == 1)
        return ;
    Node *s = list->head->next; //指向第一個結點
    Node *q = s->next;//指向第二個結點

    list->tail = s;
    list->tail->next = NULL; //斷開連結串列
    //按值插入
    while(q!=NULL)
    {
        s = q;
        q = q->next;

       //p指標為已排序指標,s為待排序指標
       Node *p = list->head;
       while(p->next != NULL && p->next->data < s->data)
            p=p->next;

       //若插入到尾結點,則更新尾結點
       if(p->next == NULL)
       {
            list->tail = s;
       }


       //尾插
       s->next = p->next;
       p->next = s; //指向新建立的結點


    }
}
//把整個單鏈表斷開,把剩下連結串列的結點按值頭插
void reverse(List *list)
{
    if(list->size==0 || list->size == 1)
        return ;
    Node *p = list->head; //指向第一個結點
    Node *q = p->next;//指向第二個結點

    list->tail = p;//指向第一個結點
    list->tail->next = NULL; //斷開連結串列


    while(q != NULL)
    {
        p = q;
        q = q->next;

        p->next = list->head->next ;
        list->head->next  = p; //在頭結點和第一個結點直接插入



    }
}

void clear(List *list)
{
    if(list->size ==0)
        return ;
    Node *p = list->head->next;
    while(p!=NULL)
    {
        list->head->next = p->next;
        free(p);
        p = list->head->next;
    }
    list->tail = list->head;

    list->size = 0;
}

void destroy(List *list)
{
    clear(list);
    free(list->head);
    list->tail = list->head =NULL;
}
void show_list(List *list)
{
    Node *p = list->head->next;
    while(p)
    {
        printf("%d--->",p->data);
        p = p->next;
    }
    printf("Nul\n");
}

main.cpp

#include "sList.h"

/*
break語句通常用在迴圈語句和開關語句中。
當break用於開關語句switch中時,可使程式跳出switch而執行switch以後的語句;如果沒有break語句,則會從滿足條件的地方(即與switch(表示式)括號中表達式匹配的case)開始執行,直到switch結構結束。

當break語句用於do-while、for、while迴圈語句中時,可使程式終止迴圈。而執行迴圈後面的語句,通常break語句總是與if語句聯在一起。即滿足條件時便跳出迴圈。
*/

int main()
{
    List mylist;
    InitList(&mylist);

    int select;
    ElemType num;
    Node *p;
    while(select)
    {
        printf("* [1] push_back                [2] push_front  *\n");
        printf("* [3] show_list                [4] pop_back     *\n");
        printf("* [5] pop_front                [6] insert_val   *\n");
        printf("* [7] find                     [8] length       *\n");
        printf("* [9] delete_val               [10] sort        *\n");
        printf("* [11] reverse                 [12] clear       *\n");
        printf("* [13] destroy                 [0] quit_system  *\n");
        printf("please choose:>");
        scanf("%d",&select);
        if(select == 0)  break;
        switch(select)
        {
            case 1:
                printf("push_back: input numbers(-1 erminate):");
                while(scanf("%d",&num),num != -1)
                {
                    push_back(&mylist,num);  //尾插
                }
                break;
            case 2:
                printf("push_front: input numbers(-1 erminate):");
                while(scanf("%d",&num),num != -1)
                {
                    push_front(&mylist,num);  //頭插
                }
                break;
            case 3:
                show_list(&mylist);
                break;
            case 4:
                pop_back(&mylist); //尾刪
                break;
            case 5:
                pop_front(&mylist); //頭刪
                break;
            case 6:
                printf("input inset value:>");
                scanf("%d",&num);
                insert_val(&mylist,num); //升序插值
                break;
            case 7:
                printf("input find value:>");
                scanf("%d",&num);
                p = find(&mylist,num); //查詢某值
                if(p == NULL)
                    printf("value %d not exist\n",num);
                break;
            case 8:
                printf("length:%d\n",length(&mylist));  //長度
            case 9:
                printf("input delete value:>");
                scanf("%d",&num);
                delete_val(&mylist,num); //刪除值
                break;
            case 10:
                sort(&mylist); //升序
                break;
            case 11:
                reverse(&mylist); //反轉
                break;
            case 12:
                clear(&mylist); //清除
                break;
            case 13:
                destroy(&mylist); //銷燬
                break;
            default:
                printf("error,choose again\n");
                break;
        }

    }

    printf("Hello world!");
    return 0;
}