选择排序
- 假设升序
排序思想
选择排序的思想是每一趟在n-i+1个记录中选取关键字最小的记录,作为有序序列的第i个记录,并和第i个记录交换
在将要排序的区间中选择出最小的一个数字,与排序区间中的第一个数进行交换,让后依次缩小区间,直到区间只剩下一个数字为止
算法总共分为三步:选择数据->将数据放入正确的位置->缩小排序范围
如图,待排序的数组为{9,1,5,8,3,7,4,6,2},用min表示有序区间最小值的下标,然后对待排序区间进行遍历与最小值的下标进行交换,遍历一遍,找到最小的一个下标,最终与待排序区间的第一个值进行交换,最后缩小排序范围
代码实现如下:
#include<iostream>
#include<assert.h>
using namespace std;
//简单选择排序 9,1,5,8,3,7,4,6,2
void SelectSort1(int arr[], int sz)
{
assert(arr);
int left = 0;//记录排序区间的首下标
while (left < sz)//对前sz-1个数进行比较
{
int begin = left;//遍历有序区间
int min = left;//记录有序区间中的最小值
while (begin <= sz)//要遍历完有序区间中的每一个数
{
if (arr[min]>arr[begin])
{
min = begin;
}
++begin;//对待排序区间中的数字逐一进行比较
}
swap(arr[min], arr[left]);
++left;//缩小有序区间
}
}
void Printf(int arr[], int sz)
{
assert(arr);
for (int i = 0; i < sz; i++)
{
cout << arr[i]<<" ";
}
}
int main()
{
int arr[] = { 9, 1, 5, 8, 3, 7, 4, 6, 2 };
int sz = sizeof(arr) / sizeof(arr[0]);
SelectSort1(arr, sz-1);
Printf(arr, sz);
system("pause");
}
选择排序的优化:
上面的选择排序中,每一趟只能找出一个最小值,那么要找N趟,每躺要比较/交换N次,所以时间复杂度为O(n^2)
优化的方法是:每一趟排序可以找出一个最大值和最小值,将找出的最小值放在数组的待第一位,将找出的最大值放在数组的待最后位,排序区间的那么比较的就缩小为N/2趟
代码实现如下:
#include<iostream>
#include<assert.h>
using namespace std;
//简单选择排序 9,1,5,8,3,7,4,6,2
void SelectSort2(int arr[], int sz)
{
assert(arr);
int left = 0;//待排序区间的首下标
int right = sz;//待排序区间的尾下标
while (left < right)//遍历整个有序区间
{
int max = right;//记录最大值的下标
int min = left;//记录最小值的下标
int begin = left;//遍历待排序区间
while (begin <= right)//遍历待排序有序区间
{
if (arr[begin]<arr[min])
{
min = begin;
}
if (arr[begin]>arr[max])
{
max = begin;
}
++begin;
}
swap(arr[min], arr[left]);
if (max == left)//处理第一个值为最大值的情况
{
max = min;
}
swap(arr[max], arr[right]);
++left;
--right;
}
}
void Printf(int arr[], int sz)
{
assert(arr);
for (int i = 0; i < sz; i++)
{
cout << arr[i]<<" ";
}
}
int main()
{
int arr[] = { 9, 1, 5, 8, 3, 7, 4, 6, 2 };
int sz = sizeof(arr) / sizeof(arr[0]);
SelectSort2(arr, sz - 1);
Printf(arr, sz);
system("pause");
}
时间复杂度&&空间复杂度分析:
无论最好的情况还是最坏的情况,比较的次数都是一样多,第i趟排序需要第n-i次的比较,所以需要比较的次数为:n-1+n-2+n-3+…+2+1=n*(n-1)/2,最好的情况,只交换一次,最差的时候,交换n-1次,所以总的时间复杂度为O(n^2)
- 时间复杂度:O(n^2)
- 空间复杂度:O(1)
堆排序
堆排序的思想主要分为两步:
- 将一个无序序列构建成一个堆
首先根据所给元素进行建堆,从下往上,从右向左将每个非叶子节点当作根节点,将其和其子树调整成大堆,如图为建堆的整个过程
- 输出堆顶元素,将剩余元素调整为一个新的堆
当大堆建好之后,堆顶 元素一定是堆中元素的最大值,将堆顶元素与堆尾du素交换,再对交换后剩下的元素重新建堆
代码实现如下:
- 输出堆顶元素,将剩余元素调整为一个新的堆
#include<iostream>
#include<assert.h>
using namespace std;
//堆排序 90,10,50,80,30,70,40,60,20
void AdjustDown(int *arr, int father, int sz)
{
assert(arr);
int child = father * 2 + 1;
while (child < sz)
{
if (child + 1 < sz && arr[child+1] > arr[child])
{
child++;
}
if (arr[child]>arr[father])
{
swap(arr[child], arr[father]);
father = child;
child = father * 2 + 1;
}
else
return;
}
}
void HeapSort(int *arr, int sz)
{
assert(arr&&sz > 0);
//默认建大堆
int i = 0;
for (i = (sz - 2) / 2; i >=0; i--)
{
AdjustDown(arr, i, sz);
}
//建好堆之后,堆顶元素一定是堆中最大的元素,将堆顶元素与堆尾元素进行交换
while (sz>1)//要排序的数据个数必须大于1
{
sz--;
swap(arr[0], arr[sz]);
AdjustDown(arr, 0, sz);
}
}
void Printf(int arr[], int sz)
{
assert(arr);
for (int i = 0; i < sz; i++)
{
cout << arr[i]<<" ";
}
}
int main()
{
int arr[] = { 90, 10, 50, 80, 30, 70, 40, 60, 20 };
int sz = sizeof(arr) / sizeof(arr[0]);
HeapSort(arr, sz);
Printf(arr, sz);
system("pause");
}
时间复杂度&&空间复杂度分析
- 建堆的时间复杂度为O(n)
- 正式排序进行重建堆的时间复杂度为O(nlogn)
- 堆排序堆原始状态并不敏感,所以堆排序的最好、最坏、平均时间复杂度都是O(nlogn)
- 在性能上高于冒泡排序,选择排序以及直接插入排序的O(n^2)
- 空间复杂度上,堆排序是一种不稳定的排序
- 不适合待排序序列个数较少的情况