排序算法
直接插入排序算法:每趟将一个待排序的关键字按照其值的大小插入到已经排好的部分有序序列的适当位置上,直到所有待排关键字都被插入到有序序列中为止
void InsertSort(int R[], int n) //代拍关键字存储在R[]中,默认为整形,个数为n
{
int i = 0, j = 0;
int temp = 0;
for (i = 1; i < n; ++i)
{
temp = R[i]; //将待插入关键字暂存于temp中
j = i + 1;
//下面这个循环完成待排关键字之前的关键字开始扫描,如果大于待排关键字,则后移一位
while (j >= 0 && temp < R[j])
{
R[j + 1] = R[j];
--j;
}
R[j + 1] = temp; //找到插入位置,将temp中暂存的待排关键字插入
}
}
//直接插入算法的时间复杂度为O(n*n)
希尔排序:希尔排序又称之为缩小增量排序,其本质还是插入排序,只不过是将待排序列按照某种规则分成几个子序列
,分别对这几个子序列进行直接插入排序。这个规则的体现就是增量的选取.希尔排序的时间复杂度为O(n*logn)
void Shellsort(int Array[], int n)
{
int d = n / 2; //设置起始增量
while (d >= 1) //增量为1时排序结束
{
for (int k = 0; k < d; ++k) //遍历所有的子序
{
for (int i = k + d; i < n; i += d) //对每个子序进行插入排序
{
int temp = Array[i];
int j = i - d;
while (j >= k && Array[j] > temp)
{
Array[j + d] = Array[j];
j -= d;
}
Array[j + d] = temp;
}
}
d = d / 2; //缩小增量
}
}
//冒泡排序:时间复杂度为O(n*n)
void BubbleSort(int R[], int n) //默认待排序关键字为整型
{
int i = 0, j = 0, flag = 0;
int temp;
for (int i = n - 1; i >= 1; --i)
{
flag = 0; //变量flag用来标记本堂排序是否发生了交换
for (j = 0; j < i; ++j)
if (R[j - 1] > R[j])
{
temp = R[j];
R[j] = R[j - 1];
R[j - 1] = temp;
flag = 1; //如果没有发生交换,则flag的值为0,;如果发生了交换,flag的值改为1
}
if (0 == flag) //一趟排序过程中如果没有发生关键字交换,则证明序列有序,排序结束
return;
}
}
快速排序;也是交换类的排序,它通过多次划分操作实现排序。以升序为例,其执行流程可以概括为:每一趟选择当前所有子序列中的一个关键字(通常是第一个)作为枢轴,将子序列中比枢轴小的移到枢轴的前边,比枢轴大的移动到枢轴的后边;当本趟所有的子序列都被枢轴以上述规则划分完毕后会的到新的一组更短的子序列,它们成为下一趟划分的初始序列集。快速排序的算法思想基于分治思想的,其平均时间复杂度为O(n*logn),最坏时间复杂度为O(n*n)
void QuickSort(int R[], int low, int high) //对从R[Low]到R[High]的关键字进行排序
{
int temp = 0;
int i = low, j = high;
if (low < high)
{
temp = R[low];
//下面这个循环完成了一趟排序,即数组中小于temp的关键字放在左边,大于temp的关键字放在右边。左边和右边的分界点就是temp的最终位置
while (i < j)
{
while (i < j && R[j] >= temp) //先从右往左扫描,找到第一个小于temp的关键字
--j;
if (i < j) //这个判断保证退出上面的while循环是因为R[j] < temp,而不是因为 i>= j退出循环的,此步非常重要切忌将其忽略
{
R[i] = R[j]; //放在temp左边
++i; //i右移一位
}
while (i < j && R[i] <= temp) //从右往左扫描,找到一个大于temp的关键字
++i;
if (i < j)
{
R[j] = R[i]; //放在tem的左边
--j; //j右移一位
}
}
R[j] = temp; //将temp放在最终的位置上
QuickSort(R, low, i - 1); //递归的对temp左边的关键字进行排序
QuickSort(R, i + 1, high); //递归的对temp右边的关键字进行排序
}
}
简单选择类排序:选择类排序的主要动作是“选择”。简单选择采用最简单的选择方式,从头至尾扫描序列,选出
最小的一个关键字,和第一个关键字交换,接着从剩下的关键字中继续这种选择和交换,最终使序列有序
void SelectSort(int R[], int n)
{
int i = 0, j = 0, k = 0;
int temp = 0;
for (i = 0; i < n; ++i)
{
k = i;
//下面这个循环是算法的关键,它从序列中挑选出最小的一个关键字
for (j = i + 1; j < n; ++j)
{
if (R[k] > R[j])
k = j;
}
//下面三句完成最小关键字与无序序列的第一个关键字的交换
temp = R[i];
R[i] = R[k];
R[k] = temp;
}
}
堆排序:对是一种完全二叉树,这颗二叉树满足:任何一个非叶结点的值都不大于(或小于)其左右孩子结点的值。若父亲大孩子小,这样的堆称之为大顶堆;若父亲小孩子大称为小根堆。
根据堆的定义可以知道,代表堆的这颗完全二叉树的根结点是最大的(或者最小的),因此将一个无序的序列调整为一个堆,就可以找到这个序列的最大值(或者最小)的值,然后将找出的值交换到这个序列的最后(或最前),这样有序序列关键字增加1个,无序序列中的关键字减少1个,对新的无序序列重复这样的操作,就实现了排序。这就是堆排序的思想
堆排序中最关键的操作是将序列调整为堆。整个排序的过程就是通过不断调整,使得不符合堆定义的完全二叉树变为符合堆定义的完全二叉树
堆的插入关键字:需要在插入结点之后保持堆的性质,即完全二叉树形态与父大子小性质(以大根堆为例),因此需要先将要插入的结点X放在最底层的最右边,插入后满足完全二叉树的特点,然后把X依次向上调整到合适位置上以满足父大子小的性质
堆中删除结点:删除堆中一个结点时,原来的位置就会出现一个孔,填充这个孔的方法就是:把最底层最右边的叶子值赋给该孔并下调到合适的位置,最后把
该叶子结点点删除堆排序执行过程描述(以大根堆为例):
1)从无序序列所确定的完全二叉树的第一个非叶子结点开始,从左至右,从上至下,对每个结点进行调整,最终得到一个大根堆
对结点的调整方法:将当前结点(假设为A)的值与其孩子结点进行比较,如果存在大于A的值的孩子结点,则从中挑出最大的一个与A进行交换。当A来到
下一层的时候重复上述过程,直到A的孩子结点的值都小于A的值为止。
2)将当前的无序序列中的第一个关键字,反应在树的根结点(假设为B)与无序序列中的最后一个关键字交换(假设为C)。B进入有序序列,达到最终位置。
无序序列中的关键字个数减少1个,有序序列中的关键字个数增加1个,此时只有结点C可能不满足堆的定义,对其进行调整
3)重复上述第2)步,直到无序序列中的关键字个数为1时结束排序
代码如下:
//本函数完成在数组R[Low]到R[High]的范围内对在位置Low上的结点进行调整
void Sift(int R[], int Low, int High)//这里关键字的存储设定为从数组下标为1开始
{
int i = Low, j = 2 * i; //R[j]是R[i]的左孩子
int temp = R[i];
while (j <= High)
{
if (j < High && R[j] < R[j + 1]) //若右孩子较大,则把j指向右孩子
++j; //j变为 2*i+1
if (temp < R[j])
{
R[i] = R[j]; //将R[j]调整到双亲结点的位置上
i = j;
j = 2 * i; //修改i和j的值,以便继续向下调整
}
else break;
}
R[i] = temp; //被调整结点的值放入最终位置
}
//堆排序函数
void HeapSort(int R[], int n)
{
int i = 0;
int temp = 0;
for (i = n / 2; i >= 1; --i) //初始化堆
Sift(R, i, n);
for (i = n; i >= 2; --i) //进行n-1次循环完成堆排序
{
//下面三句换出了根结点中的关键字,将其放入最终的位置
temp = R[1];
R[1] = R[i];
R[i] = temp;
Sift(R, 2, i - 1); //在减少了1个关键字的无序序列中进行调整
}
堆排序算法所需的空间复杂度为O(1),这是它相对于归并排序的优点。时间复杂度在任何情况下均为O(n*logn),这是它相对于快速排序的最大优点, 快速排序最坏的时间复杂度为O(n*n)。
堆排序适用场景是关键字数目特别多的情况下,典型的例子是从10000个关键字选出前10个最小的。这种情况下用堆排序最好。如果关键字数目较少,则不建议使用堆排序
//STL中已经写好了堆排序,一般如果是自己在实践中需要用的是由直接调用下面两句即可
make_heap(_First, _Last, _Comp); //默认是建立最大堆的。对int类型,可以在第三个参数传入greater<int>()得到最小堆。
sort_heap(_First, _Last); //排序之后就不再是一个合法的heap了
make_heap(b, b + 10 ,greater<int> ()); //建立小根堆
sort_heap(b, b + 10, greater<int>()); //从大到小进行排序
make_heap()建堆的时候,默认是大根堆,第三个参数用greater<T>会变成小根堆;
sort_heap()排序的时候,默认是从小到大,但是第三个参数用greater<T>会变成从大到小
并且sort_heap的第三个参数要和make_heap的第三个参数一致,否则程序运行时会报错
//上面这两个函数原型为:
template <class RandomAccessIterator>
void make_heap (RandomAccessIterator first, RandomAccessIterator last);
template <class RandomAccessIterator, class Compare>
void make_heap (RandomAccessIterator first, RandomAccessIterator last,
Compare comp );
template<class _RanIt> inline
void sort_heap(_RanIt _First, _RanIt _Last)
{ // order heap by repeatedly popping, using operator<
_STD sort_heap(_First, _Last, less<>());
}
*/