选择排序
思想
在由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次,总的排序码的比较次数为: ∑n−2i=0(n−i−1)=n(n−1)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
算法解答
- 如何寻找父节点?设外结点下标为
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");
}
算法分析
时间复杂度
胜者树是一颗完全二叉树,设待排序元素
n
个,建立胜者树需两两比价
空间复杂度
算法使用了一棵胜者树,有 n−1 个结点,空间代价为 O(n) 。
算法的稳定性
算法是稳定的,因为当两两比较相等时总是左兄弟上升。
注:本文参考书籍《数据结构精讲与习题详解—考研辅导与答疑解惑》,殷人昆编著,清华大学出版社。