內部排序算法1(插入排序)

內部排序算法1(插入排序)

重點指標
1. 排序算法的時間代價分析
2. 排序算法的空間代價分析
3. 排序方法的穩定性
4. 靜態排序和動態排序
5. 算法的適用性

排序
1. 排序是對數據元素的邏輯順序或者物理順序的一種重新排列。
2. 排成非減(遞增)順序稱爲正序, 排成非增(遞減)順序稱爲逆序。
3. 排序的依據是排序碼(可重複),即元素或記錄中的用於作爲排序依據的項。
4. 靜態排序在排序過程中鏈表結構不變,可以避免數據移動,但是代價是增加了使用的指針空間。

排序算法的穩定性的判斷原則
1. 如果需要把所有元素按排序的升序排列,在每趟從前向後比較選小時把排序碼相等的視爲更小者,或從後向前比較選大時把排序碼相等的視爲更大者,使得原本正序的元素視爲逆序,一旦交換將導致兩個排序碼相等的元素髮生相對位置的前後顛倒。
2. 在排序的過程中,需要以一個較大的間隔互換數據,或把數據隔空搬運一段較大距離時,排序方法一定不穩定,它可能會把原先排在前面的元素搬到具有相同排序碼的另一個元素的後面,顛倒了具有相同排序碼的不同元素的相對前後位置。
3. 不穩定的排序算法有4種:Shell排序、簡單選擇排序、快速排序和堆排序。
4. 穩定排序算法有 種,直接插入排序、折半插入排序、氣泡排序、歸併排序、錦標賽排序和基數排序。

排序算法的適用於那種數據結構(順序結構和單鏈表結構)
1. 適用於順序表的排序方法有:直接插入排序、折半插入排序、希爾排序、氣泡排序、快速排序、簡單選擇排序、堆排序、歸併排序、計數排序和奇偶排序等。
2. 適用於單鏈表的排序方法有:直接插入排序、氣泡排序、簡單選擇排序、歸併排序和基數排序。
3. 適用於樹形排序的排序方法有:錦標賽排序(勝者樹)、多路歸併排序(敗者樹)、 二叉查找樹排序、堆排序等。


排序算法的介紹

1. 插入排序

思路

將待排序的子序列中的一個元素按其排序碼的大小插入到已經排好序的有序子序列中的適當位置,使得有序子序列擴大一個元素。如此繼續,直到待排序子序列中的所有元素取空並都插入到有序子序列。

思想

把數組a[n]中待排序的n個元素看成一個有序表和一個無序表,開始時有序表中只包含一個元素a[0],無序表中包含有n-1個元素a[1]~a[n-1]。排序過程中每次從無序表中退成第一個元素,把它插入到有序表中的適當位置,使之成爲新的有序表,這樣經過n-1次退出和插入後,無序表就變成空表了,有序表中就包含了所有的元素。

圖示


這裏寫圖片描述
圖片來自:幾種基本的插入排序

算法描述

//插入排序頭文件
#pragma once
#include<iostream>
typedef int DataType;

class InsertSort
{
public:
    InsertSort(int length);
    void create();
    void sort();
    void print();

private:
    DataType *data;
    int len;
};

InsertSort::InsertSort(int length)
{
    len = length;
    data = new DataType[len];
}

inline void InsertSort::create()
{
    std::cout << "請輸入要排序的序列: " << std::endl;
    for (int i = 0; i < len; i++)
    {
        int temp;
        std::cin >> temp;
        data[i] = temp;
    }
    std::cout << "輸入完畢" << std::endl;
}

inline void InsertSort::sort()
{
    int i, j;
    for (i = 1; i < len; i++)
    {
        if (data[i] < data[i - 1])      //當序列放生逆序的時候才插入,否則跳過。
        {
            int temp = data[i];         //記錄需要排序的那個值,然後從後往前比較並移動數據。
            for (j = i - 1; temp < data[j] && j >= 0; j--)
            {
                data[j + 1] = data[j];
            }
            data[j + 1] = temp;
        }
    }
}

inline void InsertSort::print()
{
    for (int i = 0; i < len; i++)
    {
        std::cout << data[i] << " ";
    }
    std::cout << std::endl;
}

main文件

//插入排序main文件
using namespace std;
#include"InsertSort.h"
int main() {
    InsertSort insertSort(10); //排序的序列中有十個元素
    insertSort.create();       //輸入十個數
    insertSort.sort();         //排序
    insertSort.print();        //輸出序列
    system("pause");
}

結果


這裏寫圖片描述

算法分析

事件複雜度分析

每次在有序的子序列中從後往前尋找插入元素應該插入的位置。這種方式導致算法事件代價嚴重依賴於待排序的序列的初始排列。最好的情況下,當初始排列全部有序(從小到大)的情況下,每趟插入元素只需要和有序序列的最後一個元素作比較,並不需要移動,總的比較次數(KCN)爲n-1次, 移動次數(RMN)爲0次。 在最壞的情況下,即待排序元素的序列中所有元素已經從大到小排列(反序),若想將它們翻轉過來,則第i個元素要和前面i-1個元素做比較,總的比較次數爲1+2+…+(n-1) = (n-1)*n/2次。在每一次比較之後都需要移動一次,加上取出該元素和放入指定的位置兩次,也就是ni=0(i+2)=(n+4)(n1)/2 次。在平均的情況下,比較次數和移動次數約爲n2/4 。因此,直接插入排序的時間複雜度爲O(n2)

空間複雜度分析

只需要一個存儲單元存放暫要插入的元素。即空間複雜度爲O(1)

穩定性分析

算法是穩定的,從後往前比較的時候沒有用等於號。

2. 折半插入排序

思路

設在數據表中有一個元素序列a[0]~a[n-1], 其中,a[0]~a[i-1]已經排好序。在插入a[i](i = 1, 2, …, n-1)時,利用折半查找法在有序表a[0]~a[i-1]內尋找a[i]的插入位置。

算法

//  折半插入排序頭文件
//  HalfInsertSort.h
//  HalfInsertSort
//
//  Created by zcs on 2017/4/26.
//  Copyright © 2017年 ZCS-Company. All rights reserved.
//

#ifndef HalfInsertSort_h
#define HalfInsertSort_h

typedef int DataType;

#include<iostream>

class HalfInsertSort{
    DataType *data;
    int len;

public:
    HalfInsertSort(int length);
    void create();
    void print();
    void sort();
};


HalfInsertSort::HalfInsertSort(int length)
{
    len = length;
    data = new DataType[len];
}

inline void HalfInsertSort::create()
{
    std::cout << "請輸入要排序的序列" << std::endl;
    for (int i = 0; i < len; ++i)
    {
        int temp;
        std::cin >> temp;
        data[i] = temp;
    }
    std::cout << "完成輸入" << std::endl;
}

inline void HalfInsertSort::print()
{
    for (int i = 0; i < len; ++i)
    {
        std::cout << data[i] << " ";
    }
    std::cout << std::endl;
}

inline void HalfInsertSort::sort()
{
    int i, j, low, high, mid = 0;
    DataType temp;
    for (i = 1; i < len; ++i) {
        if (data[i] < data[i - 1]) { //如果沒有發生逆序,則跳過循環。
            temp = data[i];
            low = 0;
            high = i - 1;
            while (low <= high) {    //用折半查找法查找插入的位置
                mid = low + (high - low) / 2;
                if (temp < data[mid])  //如果需要插入的值比前面的值要小,則往前移動。
                {
                    high = mid - 1;   
                }
                else  //否則,不管相等還是大於都要往後移動,保證算法的穩定性。
                {
                    low = mid + 1;
                }
            }
            for (j = i; j > low; --j) {
                data[j] = data[j - 1];
            }
            data[low] = temp;
        }
        std::cout << "第" << i << "趟: ";
        print();
    }
}

#endif /* HalfInsertSort_h */
//  折半插入排序main文件
//  main.cpp
//  HalfInsertSort
//
//  Created by zcs on 2017/4/26.
//  Copyright © 2017年 ZCS-Company. All rights reserved.
//

#include <iostream>
#include "HalfInsertSort.h"

int main(int argc, const char * argv[]) {
    HalfInsertSort sort(10);
    sort.create();
    sort.sort();
    return 0;
}

結果


結果圖

算法分析

時間複雜度

排序碼的比較次數與待排序元素的初始排列無關,插入第i(i=1, 2, …, n-1)個元素時,排序碼比價次數大約是logi+12 , 總的排序碼比較次數爲n1i=1(logi+12)=logn!2 , 約等於nlogn2 次。折半插入的元素移動次數與待排序的元素的初始位置有關。最好的情況下,所有的元素已經按排序碼從小到大排好序,元素的移動次數爲0次。最壞的情況下, 元素的移動次數與插入排序的情況相同,爲(n+4)(n1)/2 次。

空間複雜度

算法的空間複雜度爲O(1) , 只需要一個存儲單元來存儲將要插入的元素。

算法的穩定性

折半插入排序算法是穩定的,不會改變相同大小元素的前後順序。

與直接插入排序比較

就平均性能來說,折半插入排序比直接插入排序要快。當n比較大時,總排序碼比較次數比直接插入排序的最差情況要好得多。折半插入排序的元素移動次數與直接插入排序的移動次數相同。

3. 希爾排序

基本思想

每趟按照一個增量app作爲間隔,將全部元素序列分爲gap個子序列,所有距離爲gap的元素放在同一個子序列中,在每一個子序列中分別施行直接插入排序。然後縮小增量gap,重複上面的子序列劃分和排序工作。直到最後取gap = 1,將所有的元素放在同一個序列中排序爲止。由於開始時gap的取值較大,每個子序列中的元素較少,排序速度較快;待排序的後期,gap取值逐漸變小,子序列中元素個數逐漸變多,但由於前面工作的基礎,大多數元素已基本有序,所以排序的速度仍然很快。

圖示


這裏寫圖片描述

圖片來自:http://www.cnblogs.com/jingmoxukong/p/4303279.html

算法實現

//希爾排序頭文件
#pragma once

#include<iostream>

typedef int DataType;

class ShellSort
{
public:
    ShellSort(int length);
    void create();
    void print();
    void sort();
    ~ShellSort();

private:
    DataType *data;
    int len;

    void insertSort(int start, int gap);
};

ShellSort::ShellSort(int length)
{
    len = length;
    data = new DataType[len];
}

inline void ShellSort::create()
{
    std::cout << "請輸入要排序的序列: " << std::endl;
    for (int i = 0; i < len; i++)
    {
        int temp;
        std::cin >> temp;
        data[i] = temp;
    }
    std::cout << "輸入完畢" << std::endl;
}

inline void ShellSort::print()
{
    for (int i = 0; i < len; i++)
    {
        std::cout << data[i] << " ";
    }
    std::cout << std::endl;
}

inline void ShellSort::sort()
{
    int start, gap, i =0;
    for (gap = len / 2; gap > 0; gap /= 2)  //增量爲gap
    {
        for (start = 0; start < gap; start++)  //對每一小塊進行插入排序
        {
            insertSort(start, gap);
        }
        ++i;
        std::cout << "第 " << i << "次希爾排序: ";
        print();
    }
}

ShellSort::~ShellSort()
{
    delete[] data;
}

inline void ShellSort::insertSort(int start, int gap) //插入排序,起點爲start, step爲gap
{                                                     //對每一個部分進行插入排序。
    DataType temp;
    int i, j;
    for (i = start + gap; i < len; i = i + gap)
    {
        if (data[i] < data[i - gap])  //若沒有發生逆序,則跳出循環
        {
            temp = data[i];
            for (j = i - gap; j >= 0 && temp < data[j]; j = j - gap)
            {
                data[j + gap] = data[j];
            }
            data[j + gap] = temp;
        }
    }
}
//main函數
using namespace std;
#include"ShellSort.h"
int main() {
    ShellSort insertSort(10); //排序的序列中有十個元素
    insertSort.create();       //輸入十個數
    insertSort.sort();         //排序
    system("pause");
}

結果


希爾排序運行結果

算法分析

時間複雜度

當n很大的時候,希爾排序的排序碼平均比較次數和元素平均移動次數大約在n1.25 ~ 1.6n1.25 之間,這是利用直接插入排序作爲子序列排序方法的情況下得到的。對於規模較大的序列(n>1000)希爾排序具有很高的效率。

空間複雜度

算法的空間複雜度與直接插入排序法一致,只需要一個存儲空間儲存需要插入的元素

算法的穩定性

希爾排序是不穩定的

注:本文參考書籍《數據結構精講與習題詳解—考研輔導與答疑解惑》,殷人昆編著,清華大學出版社。

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