內部排序算法3(選擇排序)

選擇排序

思想

在由n個元素組成的序列中,選擇一個具有最小(或者最大)排序碼的元素,把它加入到有序序列中,如此繼續,直到元素序列中只剩下一個元素爲止,排序結束。


簡單選擇排序

思想

第i趟(i = 0, 1, …, n -2)從第i到第n-1個元素組成的序列中選出排序碼最小(或者最大)的元素,交換打結果序列的第i個位置。待到第n-2趟作完,待排序元素只剩下1個,就不用再選了。
1. 在一組元素a[i]~a[n-1]中選擇具有最小的排序碼的元素。
2. 若它不是這組元素中的第一個元素,則將它與這組元素中的第一個元素對調。
3. 在這組元素中剔除這個具有最小排序碼的元素,在剩下的元素a[i+1]~a[n-1]中重複執行第1和第2步,直到剩餘元素只有一個爲止。

圖示


簡單選擇排序示例圖
圖片來自:http://www.cnblogs.com/jingmoxukong/p/4303289.html

算法實現

//  簡單選擇排序頭文件
//  EasyChooseSort.h
//  EasyChooseSort
//
//  Created by zcs on 2017/4/29.
//  Copyright © 2017年 ZCS-Company. All rights reserved.
//

#ifndef EasyChooseSort_h
#define EasyChooseSort_h

#include <iostream>

typedef int DataType;

class EasyChooseSort
{
private:
    DataType *data;
    int len;

public:
    EasyChooseSort(int length);
    void create();
    void print(int num);
    void sort();
    ~EasyChooseSort();
};


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

inline void EasyChooseSort::create()
{
    std::cout << "please input the list" << std::endl;
    for (int i = 0; i < len; ++i) {
        int temp;
        std::cin >> temp;
        data[i] = temp;
    }
    std::cout << "finish" << std::endl;
}

inline void EasyChooseSort::print(int num)
{
    std::cout << "第 " << num << " 趟排序: ";
    for (int i = 0; i < len; ++i) {
        std::cout << data[i] << " ";
    }
    std::cout << std::endl;
}

inline void EasyChooseSort::sort()
{
    DataType min;
    for (int i = 0; i < len - 1; ++i) {
        int k = i;
        for (int j = i + 1; j < len; ++j) {
            if (data[j] < data[k]) {
                k = j;
            }
        }
        if (k != i) {
            min = data[i];
            data[i] = data[k];
            data[k] = min;
        }
        print(i + 1);
    }
}

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

#endif /* EasyChooseSort_h */
//  簡單選擇排序main文件
//  main.cpp
//  EasyChooseSort
//
//  Created by zcs on 2017/4/29.
//  Copyright © 2017年 ZCS-Company. All rights reserved.
//

#include "EasyChooseSort.h"

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

結果


簡單選擇排序結果

算法分析

時間複雜度

簡單選擇排序的排序碼比較次數與元素的初始排列無關。設left=0, right=n-1,第i(i = 0, 1, …, n -2)趟選擇具有最小排序碼元素所需的比價次數總數是n-i-1次,總的排序碼的比較次數爲:n2i=0(ni1)=n(n1)2 。元素的移動次數與元素的初始排列有關。當這組元素的初始狀態是按其排序碼從小到大有序的時候,每次k=i,元素的移動次數爲0。而最壞情況是每一趟都要進行交換,總得元素移動次數爲3(n-1)。

空間複雜度

算法只需要1個工作單元做數據交換使用,空間代價爲O(1)。

算法的穩定性

簡單選擇排序算法是不穩定的。簡單選擇排序算法每趟從序列中選到一個排序碼最小的元素,並與序列的第一個進行交換,如果交換前在此序列中最小排序碼元素前面有兩個排序碼相等的不同元素,其中前一個恰恰位於序列的第一個位置,一經交換把這個元素交換到另一個元素的後面去了,從而造成不穩定。
例如序列5,8,5,2,9。第一趟交換的時候序列會變成2,8,5,5,9。兩個5的前後順序發生變化,故算法是不穩定。

堆排序

思想

堆在邏輯上是一個完全二叉樹組織的非線性結構,在物理上是用一個一維數組存儲的。使用堆排序,最終要實現在現在一維數組中元素的有序排列。每次進行“對調-篩選”可在數組中從後往前將各個元素就位。排序算法的步驟如下:
1. 把數組heap中的元素序列用篩選法siftdown調整爲大根堆。
2. 令i從n-1循環到1,重複執行。
3. 處於堆頂的元素heap[0]與heap[i]對調,把最大排序碼元素交換到最後。
4. 對前面的i-1個元素,使用堆的篩選算法siftdown調整爲大根堆(即初始堆)。
5. 循環結束,最後得到全部排序好的元素排列。

圖示


堆排序存儲結構
建立初始堆
堆排序
圖片來源: http://www.cnblogs.com/jingmoxukong/p/4303826.html

算法實現

//  堆排序頭文件
//  Heap.h
//  EasyChooseSort
//
//  Created by zcs on 2017/4/29.
//  Copyright © 2017年 ZCS-Company. All rights reserved.
//

#ifndef Heap_h
#define Heap_h

typedef int ElementType;

class MaxHeap {
private:
    ElementType *elem;
    int n;

    void siftDown(int start, int end);

public:
    MaxHeap(int len);
    void create();
    void sort();
    void print();
    ~MaxHeap();
};

MaxHeap::MaxHeap(int len)
{
    n = len;
    elem = new ElementType[len];
}

inline void MaxHeap::create()
{
    std::cout << "please input the list" << std::endl;
    for (int i = 0; i < n; ++i) {
        int temp;
        std::cin >> temp;
        elem[i] = temp;
    }
    std::cout << "finish" << std::endl;
}

inline void MaxHeap::siftDown(int start, int end)
{
    int i = start, j;
    ElementType temp = elem[i];
    for (j = 2 * i + 1; j <= end; j = 2 * j + 1) {
        if (j < end && elem[j] < elem[j + 1]) {
            j++;
        }
        if (temp >= elem[j]) {
            break;
        }
        else
        {
            elem[i] = elem[j];
            i = j;
        }
    }
    elem[i] = temp;
}

inline void MaxHeap::sort()
{
    for (int i = n / 2 - 1; i >= 0; --i) {
        siftDown(i, n - 1);
    }
    for (int i = n - 1; i > 0; --i) {
        ElementType temp = elem[0];
        elem[0] = elem[i];
        elem[i] = temp;
        siftDown(0, i - 1);
    }
}

inline void MaxHeap::print()
{
    for (int i = 0; i < n; ++i)
    {
        std::cout << elem[i] << " ";
    }
    std::cout << std::endl;
}

MaxHeap::~MaxHeap()
{
    delete[] elem;
}

#endif /* Heap_h */
//  堆排序main文件
//  main.cpp
//  EasyChooseSort
//
//  Created by zcs on 2017/4/29.
//  Copyright © 2017年 ZCS-Company. All rights reserved.
//

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

int main(int argc, const char * argv[]) {
    MaxHeap heap(6);
    heap.create();
    heap.sort();
    heap.print();
    return 0;
}

結果


堆排序結果圖

算法分析

時間複雜度

siftDown算法從根到葉子節點最多篩選了log2n 次,在形成初始堆的算法中調用了n2 次siftDown算法,在排序算法中調用了n-1次siftDown算法,所以排序的時間代價爲O(nlog2n)

空間複雜度

算法只在對調元素時用了一個工作單元,空間代價爲O(1)

穩定性

堆排序算法是不穩定的。

錦標賽排序

思想

錦標賽排序又稱爲樹形選擇排序,它首先對n個元素,按其排序碼大小進行兩兩比較,得到下一輪,並把勝者(排序碼較小者)記憶下來,得到n2 個勝者。然後繼續對這些勝者進行兩兩比較,得到下一輪n22=n4 個勝者,……,如此繼續,最後決出所有n個元素的最終勝者(最小排序碼元素),並把相應外節點的排序碼改爲無窮大,重新調整勝者樹,選出新的勝者(次小排序碼元素)記憶在根節點,再輸出他,……,如此重複做下去,知道所有元素都輸出爲止。排序借宿的條件是根節點記憶了一個排序碼爲無窮大的元素。

圖示


勝者樹排序存儲結構
勝者樹排序存儲結構


這裏寫圖片描述
逗號後表示勝者的索引


勝者樹排序過程
勝者樹排序過程


圖片來源:http://www.cnblogs.com/james1207/p/3323115.html

算法解答

  1. 如何尋找父節點?設外結點下標爲j ,則i=j+n21 即爲父節點(內節點)下標,如當n=6j=7 時,i=6+721=5 即其父節點在勝者樹的下標。設內結點下標爲j , 則i=j12 即其父節點在勝者樹下標,如當n=7j=4 時,i=412=1 即其父節點在勝者樹的下標。
  2. 如何在外結點間找兄弟?如下圖。當n 爲偶數的時候,外節點都是成雙成對出現的。若外結點下標j是偶數,則它有右兄弟j+1;若j是奇數,則它有左兄弟j-1。當n時奇數,0號外結點的左兄弟是內結點n-2,其他外結點都是成雙成對出現的。若外結點的下標j是偶數,則它有左兄弟j1 ;若j 是奇數,則它有右兄弟j+1
  3. 如何在內結點間找兄弟?如下圖。當n 爲偶數,內結點都是成對成雙出現的。若內結點下標j(>0) 是偶數,則它有左兄弟j1 ;若j 是奇數,則它有有兄弟j+1 。當n爲奇數,下標最大(=n2) 的內結點的右兄弟是0號外結點,其他內結點都是成對成雙出現的。若內結點下標j 是偶數,則它有左兄弟j1 ;若j 是奇數,則它右兄弟j+1

    勝者樹的內、外結點
    勝者樹的內外結點

算法實現

//勝者樹頭文件
#pragma once

#include<iostream>
#include<climits>

typedef int ElementType;

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

private:
    int *elem;  //外結點
    int *w;     //內結點 
    int len;    //外結點長度

    void adjust(int start);
};

inline void WinnerTree::create()
{
    int j;
    std::cout << "please input the list: " << std::endl;
    for (int i = 0; i < len; ++i) {
        ElementType temp;
        std::cin >> temp;
        elem[i] = temp;
    }
    std::cout << "finish" << std::endl;
    for (int i = len - 1; i > 0; i = i - 2) {   //由於內部結點有n-1個,外結點有n個,故所有的結點數爲奇數個,既有最後一個外部結點必然是有左兄弟
        w[(i + len) / 2 - 1] = elem[i] < elem[i - 1] ? i : (i - 1);
    }
    if (len % 2 == 1) {   //
        w[len / 2 - 1] = elem[0] < elem[w[len - 2]] ? 0 : w[len - 2];
        j = len - 3;
    }
    else {
        j = len - 2;
    }
    for (; j > 0; j = j - 2)
    {
        w[(j - 1) / 2] = elem[w[j]] < elem[w[j - 1]] ? w[j] : w[j - 1];
    }
}

inline void WinnerTree::adjust(int start)
{
    int j = start;                         //從選手j到雙親i(勝者樹結點)
    int i = (len + j) / 2 - 1;
    if (len % 2 == 0)                     //若n爲偶數,則所有的選手都成雙出現
    {
        if (j % 2 == 0)                    //若j爲偶數,則j有右兄弟j+1
        {
            w[i] = elem[j] <= elem[j + 1] ? j : (j + 1);
        }
        else                               //當n爲奇數的時候,j與左兄弟j-1比較
        {
            w[i] = elem[j - 1] <= elem[j] ? (j - 1) : j;
        }
    }
    else
    {
        if (j == 0)                        //第0個外結點的左兄弟是第n-2個內部結點
        {
            w[i] = elem[j] < elem[w[len - 2]] ? j : w[len - 2];
        }
        else if (j % 2 == 0)
        {
            w[i] = elem[j] < elem[j - 1] ? j : (j - 1);
        }
        else
        {
            w[i] = elem[j] <= elem[j + 1] ? j : (j + 1);
        }
    }
    while (i > 0)
    {
        j = i;
        i = (i - 1) / 2;      //繼續尋找父節點
        if (len % 2 == 1 && j == len - 2)  //當內部節點和外部結點是兄弟的時候
        {
            w[i] = elem[w[len - 2]] <= elem[0] ? w[len - 2] : 0;
        }
        else if (j % 2 == 0)  //由於內部結點有根結點,故當j爲偶數的時候是有孩子,有左兄弟j-1
        {
            w[i] = elem[w[j - 1]] <= elem[w[j]] ? w[j - 1] : w[j];
        }
        else                //當j爲奇數的時候,有右兄弟j+1
        {
            w[i] = elem[w[j]] <= elem[w[j + 1]] ? w[j] : w[j + 1];
        }
    }
}

inline void WinnerTree::sort()
{
    int count = 0;
    while (count < len)
    {
        std::cout << elem[w[0]] << " ";
        count++;
        elem[w[0]] = INT_MAX;
        adjust(w[0]);
    }
    std::cout << std::endl;
}

WinnerTree::WinnerTree(int length)
{
    len = length;
    elem = new ElementType[len];
    w = new int[len - 1];
}

WinnerTree::~WinnerTree()
{
    delete[] elem;
    delete[] w;
}
//勝者樹main文件
using namespace std;
#include "WinnerTree.h"
int main() {
    WinnerTree tree(8);
    tree.create();
    tree.sort();
    system("pause");
}

算法分析

時間複雜度

勝者樹是一顆完全二叉樹,設待排序元素n 個,建立勝者樹需兩兩比價n1 次;adjust算法每次調整最多比較log2n 次,錦標賽排序調用了n 次adjust算法,排序算法的時間代價爲O(nlog2n) 。另外,錦標賽排序沒有移動元素,在勝者樹內結點僅記憶了待排序元素的下標,變動的是下標。

空間複雜度

算法使用了一棵勝者樹,有n1 個結點,空間代價爲O(n)

算法的穩定性

算法是穩定的,因爲當兩兩比較相等時總是左兄弟上升。

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

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