選擇排序
思想
在由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次,總的排序碼的比較次數爲:
空間複雜度
算法只需要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算法從根到葉子節點最多篩選了
空間複雜度
算法只在對調元素時用了一個工作單元,空間代價爲
穩定性
堆排序算法是不穩定的。
錦標賽排序
思想
錦標賽排序又稱爲樹形選擇排序,它首先對n個元素,按其排序碼大小進行兩兩比較,得到下一輪,並把勝者(排序碼較小者)記憶下來,得到
圖示
勝者樹排序存儲結構
逗號後表示勝者的索引
勝者樹排序過程
圖片來源:http://www.cnblogs.com/james1207/p/3323115.html
算法解答
- 如何尋找父節點?設外結點下標爲
j ,則i=⌈j+n2−1⌉ 即爲父節點(內節點)下標,如當n=6,j=7 時,i=⌈6+72⌉−1=5 即其父節點在勝者樹的下標。設內結點下標爲j , 則i=⌈j−12⌉ 即其父節點在勝者樹下標,如當n=7,j=4 時,i=⌈4−12⌉=1 即其父節點在勝者樹的下標。 - 如何在外結點間找兄弟?如下圖。當
n 爲偶數的時候,外節點都是成雙成對出現的。若外結點下標j是偶數,則它有右兄弟j+1;若j是奇數,則它有左兄弟j-1。當n時奇數,0號外結點的左兄弟是內結點n-2,其他外結點都是成雙成對出現的。若外結點的下標j是偶數,則它有左兄弟j−1 ;若j 是奇數,則它有右兄弟j+1 。 - 如何在內結點間找兄弟?如下圖。當
n 爲偶數,內結點都是成對成雙出現的。若內結點下標j(>0) 是偶數,則它有左兄弟j−1 ;若j 是奇數,則它有有兄弟j+1 。當n爲奇數,下標最大(=n−2) 的內結點的右兄弟是0號外結點,其他內結點都是成對成雙出現的。若內結點下標j 是偶數,則它有左兄弟j−1 ;若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");
}
算法分析
時間複雜度
勝者樹是一顆完全二叉樹,設待排序元素
空間複雜度
算法使用了一棵勝者樹,有
算法的穩定性
算法是穩定的,因爲當兩兩比較相等時總是左兄弟上升。
注:本文參考書籍《數據結構精講與習題詳解—考研輔導與答疑解惑》,殷人昆編著,清華大學出版社。