1. 程式人生 > >資料結構--連結串列的排序詳解

資料結構--連結串列的排序詳解

1、前言 
前面兩篇部落格,我已經把線性表的兩種基本的表示形式,做了一個基本的介紹和一些對比。但是,我突然發現在連結串列這裡我缺少一個很重要的內容,那就是對我們的連結串列進行排序,其實,在連線兩個連結串列的時候,就要求我們的那兩個連結串列是有序的。

2、連結串列排序—最簡單、直接的方式(直接採用冒泡或者選擇排序,而且不是交換結點,只交換資料域)
 

//線性表的排序,採用氣泡排序,直接遍歷連結串列
void Listsort(Node* & head) {
    int i = 0;
    int j = 0;
    //用於變數連結串列
    Node * L = head;
    //作為一個臨時量
    Node * p;
    Node * p1;
    //如果連結串列為空直接返回
    if (head->value == 0)return;

    for (i = 0; i < head->value - 1; i++) {
        L = head->next;
        for (j = 0; j < head->value - i - 1; j++) {
            //得到兩個值
            p = L;
            p1 = L->next;
            //如果前面的那個比後面的那個大,就交換它們之間的是資料域
            if (p->value > p1->value) {
                Elemtype temp = p->value;
                p->value = p1->value;
                p1->value = temp;
            }
            L = L->next;
        }
    }
}

因為排序中速度比較快的如快速排序是要求它們的資料域的地址是相連線的,它比較適合於順序結構,而鏈式結構的時候,我們就只有使用只會進行前後兩個比較多排序演算法,比如氣泡排序等。我們這裡是沒有交換結點的一種排序方式,這種方式簡單,明瞭,這樣就是陣列排序的時候是一樣的。後面我會寫通過交換結點的方式的排序。 
下面我們就討論討論這個排序演算法的時間複雜度了,因為它是使用氣泡排序的,它的時間只要消耗在那兩重迴圈,所以時間複雜度為:O(n*n),這個效率實在是太低,下面我們對這個想(ˇˍˇ) 想~通過另外一種方式來實現連結串列的排序

3、另外一種連結串列排序方式 
我們在討論排序演算法的時候,都是把資料存放在陣列中進行討論的,在順序結構下,我們可以採取很多高效的排序演算法,那麼這個就是我們另外一種對連結串列排序的方式,先把連結串列的內容存放到陣列中(時間為O(n)),然後,我們在對那個陣列進行排序(最快為nlog(n)),最後,存放連結串列中(時間為O(n))。通過計算,我們可以得到它的時間複雜為(O(nlogn)),這個速度已經和順序結構下差不多了,可以接受。
 

void Listsort_1(Node* & head) {
    int i = 0;
    int j = 0;
    //用於變數連結串列
    Node * L = head;
    //如果連結串列為空直接返回
    if (head->value == 0)return;
    Elemtype * copy = new Elemtype[head->value];
    //變數連結串列,存放陣列
    for (i = 0; i < head->value; i++) {
        L = L->next;
        copy[i] = L->value;
    }
    //呼叫STL中的sort函式
    sort(copy, copy + head->value);
    L = head;
    //存放回連結串列中
    for (i = 0; i < head->value; i++) {
        L = L->next;
          L->value= copy[i];
    }
}

4、先我們通過採取增大資料量的方式,比較兩種排序在效率如何

è¿éåå¾çæè¿°

 

如圖所示,在資料量為10000的時候,明顯第二種排序演算法消耗的時間比第一種快了28倍左右。

5、下面通過交換結點實現連結串列的排序 
首先我們編寫交換結點的函式,結點的交換主要就是考慮結點的指標域的問題,其中相鄰兩個結點是一種特殊的情況,要拿出來特別考慮。我們先畫出結點交換的思路圖,如下圖 
首先我們給出相鄰兩個結點交換的思路: 
 

è¿éåå¾çæè¿°

下面是普通情況下的交換如下圖 

è¿éåå¾çæè¿°

 

//引數為頭結點和需要交換的兩個結點的位置(起點為1)
void swap_node(Node * & head,int i,int j) {
    //位置不合法
    if (i<1 || j<1 || i>head->value || j >head->value) {
        cout << "請檢查位置是否合法" << endl;
        return;
    }
    //同一個位置不用交換
    if (i == j)
    {
        return;
    }
    //相鄰兩個交換比較簡單
    if (abs(i - j) == 1) {
        //位置靠前的那個結點的前一個結點
        Node * pre;
        if (i < j)
            pre = getitem(head, i);
        else
            pre = getitem(head, j);

        //儲存第一個結點
        Node * a = pre->next;
        //儲存第二結點
        Node * b = a->next;
        //改變pre下一個結點的值
        pre->next = b;
        //必須先把b的下一個結點值給a先
        a->next = b->next;
        //讓b的下一個結點等於a
        b->next = a;
        return;
    }

    //第一個結點前一個結點
    Node * a = getitem(head, i);
    //第二個結點的前一個結點
    Node * b = getitem(head, j);
    //第一個結點
    Node * p = a->next;
    //第二個結點
    Node * q = b->next;
    //第一個結點的下一個結點
    Node * p_next = p->next;
    //第二結點的下一個結點
    Node * q_next = q->next;

    //a的下一個結點指向第二個結點q
    a->next = q;
    //第二結點的下一個結點指向第一個結點的下一個結點
    q->next = p_next;
    //b的下一個結點指向第一個結點p
    b->next = p;
    //第一個結點的下一個結點指向第二個結點的下一個結點
    p->next = q_next;

}

 排序時候的程式碼,切記交換結點都是前後結點交換,所以交換完成後,L就已經被移動到下一個結點了,故不要再執行:L=L->next

 

//線性表的排序,交換結點
void Listsort_Node(Node* & head) {
    int i = 0;
    int j = 0;
    //用於變數連結串列
    Node * L = head;
    //作為一個臨時量
    Node * p;
    Node * p1;
    //如果連結串列為空直接返回
    if (head->value == 0)return;
    int flag = 0;
    cout << head->value << endl;
    for (i = 0; i < head->value - 1; i++) {
        L = head->next;
        for (j = 0; j < head->value - 1 - i; j++) {
            //如果我們交換了結點,那麼我們就已經在交換結點的時候,把L移動到下一個結點了,所以不要
            //再執行:L = L->next;,否則會報錯的
            if (L->value > L->next->value) {
                flag = 1;
                swap_node(head, j + 1, j + 2);

            }
            if (flag == 1) {
                flag = 0;
            }
            else {
                L = L->next;
            }

        }   
    }
}

好了,今天的就寫到這裡了,今天通過寫交換結點,發現連結串列真的很容易忽悠人,我就被忽悠了一個小時,才知道那個結點已經被移動到下一個結點了。

最後,補充一個實現連結串列反轉的好方法(感覺你在標頭檔案裡面計算一下連結串列的長度可以帶來很多遍歷的)

 

void rollback(Node * & head) {
    //先知道了最後一個元素和第一個元素的位置
    int end = head->value;
    int start = 1;
    //兩邊同時開工
    //進行調換
    while (1) {
        if (end == start)
            return;
        swap_node(head, end, start);
        --end;
        ++start;
    }
}

希望大家,對我寫的程式碼做出一些評價。我想想還是直接貼個完成的程式碼出來好了,調轉程式碼也在裡面了

 

#include<iostream>
#include<ctime>
#include<cstdlib>
#include<windows.h>
#include<algorithm>
using namespace std;
typedef int Elemtype;

//鏈式結構,我們打算在連結串列中新增一個
//儲存長度的頭結點,加入這個結點可以方便我們對結點做一些
//基本的操作,結點儲存的是線性表的長度
struct Node
{
    //結點的值,如果是頭結點,儲存是連結串列的長度
    Elemtype value;
    //下一個結點的地址
    Node * next;

};

//建立一個空連結串列,每個頭結點就代表一個連結串列
void InitList(Node * & head) {
    head = new Node();
    head->value = 0;
    head->next = NULL;
}
//銷燬一個連結串列
void DestroyList(Node * & head) {
    delete head;
    head = NULL;
}

//清空整個列表
void ClearList(Node * & head) {
    head->value = 0;
    head->next = NULL;
}

//插入函式
bool Listinsert(Node * & head, int i, Elemtype value) {

    //插入到前面的方法
    int j = 0;
    Node * L = head;
    //如果插入的位置不合法,直接返回錯誤提示
    if (i<1 || i>head->value + 1)return false;

    //得到插入位置的前一個結點
    while (j < i - 1) {
        L = L->next;
        ++j;
    }

    //s是一個臨時結點
    Node * s = new Node();
    s->value = value;    //先對臨時結點賦值
    s->next = L->next;   //讓臨時結點下一個位置指向當前需要插入前一個結點的下一個位置
    L->next = s;          //讓前一個結點下一個位置指向臨時結點,完成
                          //線性表的長度加一
    ++head->value;
    return true;
}
//得到某個位置上的值
Node * getitem(Node * & head, int i) {
    //我們要求程式返回特定位置上的值
    //我們一樣是從頭結點開始尋找該位置
    int j = 0;
    Node * L = head;
    //想要的那個位置是否合法
    if (i<1 || i >head->value)return NULL;

    //同樣是先得到前一個結點
    while (j < i - 1) {
        L = L->next;
        ++j;
    }
    //value = L->next->value;
    return L;
}
//線性表的排序,採用氣泡排序,直接遍歷連結串列
void Listsort(Node* & head) {
    int i = 0;
    int j = 0;
    //用於變數連結串列
    Node * L = head;
    //作為一個臨時量
    Node * p;
    Node * p1;
    //如果連結串列為空直接返回
    if (head->value == 0)return;

    for (i = 0; i < head->value - 1; i++) {
        L = head->next;
        for (j = 0; j < head->value - i - 1; j++) {
            //得到兩個值
            p = L;
            p1 = L->next;
            //如果前面的那個比後面的那個大,就交換它們之間的是資料域
            if (p->value > p1->value) {
                Elemtype temp = p->value;
                p->value = p1->value;
                p1->value = temp;
            }
            L = L->next;
        }
    }
}
//通過陣列來完成我的排序
void Listsort_by_array(Node* & head) {
    int i = 0;
    int j = 0;
    //用於變數連結串列
    Node * L = head;
    //如果連結串列為空直接返回
    if (head->value == 0)return;
    Elemtype * copy = new Elemtype[head->value];
    //變數連結串列,存放陣列
    for (i = 0; i < head->value; i++) {
        L = L->next;
        copy[i] = L->value;
    }
    //呼叫STL中的sort函式
    sort(copy, copy + head->value);
    L = head;
    //存放回連結串列中
    for (i = 0; i < head->value; i++) {
        L = L->next;
        L->value = copy[i];
    }
}

//引數為頭結點和需要交換的兩個結點的位置(起點為1)
void swap_node(Node * & head,int i,int j) {
    //位置不合法
    if (i<1 || j<1 || i>head->value || j >head->value) {
        cout << "請檢查位置是否合法" << endl;
        return;
    }
    //同一個位置不用交換
    if (i == j)
    {
        return;
    }
    //相鄰兩個交換比較簡單
    if (abs(i - j) == 1) {
        //位置靠前的那個結點的前一個結點
        Node * pre;
        if (i < j)
            pre = getitem(head, i);
        else
            pre = getitem(head, j);

        //儲存第一個結點
        Node * a = pre->next;
        //儲存第二結點
        Node * b = a->next;
        //改變pre下一個結點的值
        pre->next = b;
        //必須先把b的下一個結點值給a先
        a->next = b->next;
        //讓b的下一個結點等於a
        b->next = a;
        return;
    }

    //第一個結點前一個結點
    Node * a = getitem(head, i);
    //第二個結點的前一個結點
    Node * b = getitem(head, j);
    //第一個結點
    Node * p = a->next;
    //第二個結點
    Node * q = b->next;
    //第一個結點的下一個結點
    Node * p_next = p->next;
    //第二結點的下一個結點
    Node * q_next = q->next;

    //a的下一個結點指向第二個結點q
    a->next = q;
    //第二結點的下一個結點指向第一個結點的下一個結點
    q->next = p_next;
    //b的下一個結點指向第一個結點p
    b->next = p;
    //第一個結點的下一個結點指向第二個結點的下一個結點
    p->next = q_next;

}
//反轉
void rollback(Node * & head) {
    //先知道了最後一個元素和第一個元素的位置
    int end = head->value;
    int start = 1;
    //兩邊同時開工
    //進行調換
    while (1) {
        if (end <= start)
            return;
        swap_node(head, end, start);
        --end;
        ++start;
    }
}
void print(Node * & head);
//線性表的排序,採用氣泡排序,直接遍歷連結串列
//線性表的排序,交換結點
void Listsort_node(Node* & head) {
    int i = 0;
    int j = 0;
    //用於變數連結串列
    Node * L = head;
    //作為一個臨時量
    Node * p;
    Node * p1;
    //如果連結串列為空直接返回
    if (head->value == 0)return;
    int flag = 0;

    for (i = 0; i < head->value - 1; i++) {
        L = head->next;
        for (j = 0; j < head->value - 1 - i; j++) {
            //如果我們交換了結點,那麼我們就已經在交換結點的時候,把L移動到下一個結點了,所以不要
            //再執行:L = L->next;,否則會報錯的
            if (L->value > L->next->value) {
                flag = 1;
                swap_node(head, j + 1, j + 2);

            }
            if (flag == 1) {
                flag = 0;
            }
            else {
                L = L->next;
            }

        }   
    }
}

void print(Node * & head) {
    //輸出我們只需要傳入頭結點,然後迴圈判斷當前結點下一個結點是否為空,
    //這樣就可以輸出所有內容
    Node * L = head;
    while (L->next) {
        L = L->next;
        cout << L->value << " ";
    }
    cout << endl;
}
int main() {
    //連結串列的頭結點,不存放任何值,首先初始化頭結點
    Node * head;

    Node * head_array;
    Node * head_node;
    Node * head_roll;
    srand((int)time(NULL));     //每次執行種子不同,生成不同的隨機數
    //建立一個連結串列

    InitList(head); 
    InitList(head_array);
    InitList(head_node);
    InitList(head_roll);

    int i;
    cout << "請輸入需要插入元素個數" << endl;
    int n;
    cin >> n;//5
    //cout << "請輸入" << n << "個值" << endl;
    for (i = 0; i < n; i++) {
        Elemtype temp;
        temp = rand();
        if (!Listinsert(head, i + 1, temp)) {
            cout << "插入元素失敗" << endl;
        }
        if (!Listinsert(head_array, i + 1, temp)) {
            cout << "插入元素失敗" << endl;
        }
        if (!Listinsert(head_node, i + 1, temp)) {
            cout << "插入元素失敗" << endl;
        }
        if (!Listinsert(head_roll, i + 1, temp)) {
            cout << "插入元素失敗" << endl;
        }

    }
    cout << "初始化結果" << endl;
    print(head);
    cout << "反轉結果" << endl;
    rollback(head_roll);
    print(head_roll);
    cout << "氣泡排序(資料域交換)" << endl;
    Listsort(head);
    print(head);
    cout << "借陣列為媒介進行排序(資料域交換)" << endl;
    Listsort_by_array(head_array);
    print(head_array);
    cout << "氣泡排序(結點交換)" << endl;
    Listsort_node(head_node);
    print(head_node);
    system("pause");
    return 0;

}

執行環境:vs2015 
輸出結果: 
 

è¿éåå¾çæè¿°


來源: https://blog.csdn.net/qq_35644234/article/details/53222603