归并排序
归并排序介绍
归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
作为一种典型的分而治之思想的算法应用,归并排序的实现由两种方法:
1.自上而下的递归(所有递归的方法都可以用迭代重写,所以就有了第 2 种方法);
2.自下而上的迭代;
算法步骤
1.申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
2.设定两个指针,最初位置分别为两个已经排序序列的起始位置;
3.比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
4.重复步骤 3 直到某一指针达到序列尾;
5.将另一序列剩下的所有元素直接复制到合并序列尾。
动图演示
代码实现
#include <iostream>
using namespace std;
template <class T>
void MSort(T a[], int left, int right)
{
if (left < right)
{
int center = (left + right) / 2;//取得中点
//将原来序列分为两段
MSort(a, left, center);
MSort(a, center+1, right);
//合并刚才分开的两段,得到原来序列的有序序列
Merge(a, left, center, right, right-left+1);
}
}
template <class T>
void MergeSort(T a[], int n)
{
//调用递归归并排序函数
MSort(a, 0, n-1);
}
template <class T>
void Merge(T a[], int left, int center, int right, int n)
{
T *t = new T[n];//存放被排序的元素
int i = left;
int j = center + 1;
int k = 0;
//合并数组,用插入排序,如果左边大就插入左边的数,右边的计数器等待,与下一个左边的数比较;右边大就插入右边的数,左边的计数器等待,与下一个右边的数比较(这里指的插入是插入到新数组t[])
while (i<=center && j<=right)
{
if (a[i] <= a[j])
t[k++] = a[i++];
else
t[k++] = a[j++];
}
//上面的步骤在执行完后,左或右边都有可能剩余若干个元素,而另一边的元素肯定已全部复制到新数组,这时需要特殊对待剩下的元素
if (i == center+1)
{
while (j <= right)
t[k++] = a[j++];
}
else
{
while (i <= center)
t[k++] = a[i++];
}
//把t[]的元素复制回a[]中left到right段
for (i=left,k=0; i<=right; i++,k++)
a[i] = t[k];
//释放内存
delete []t;
}
int main()
{
int intArray[5] = { 5 , 6 , 2 , 5 , 9 };
MergeSort(intArray,5);
for(int i = 0; i < 5; i++)
cout << intArray[i] << endl;
}
基数排序
基数排序介绍
基数排序(Radix Sort)是桶排序的扩展,它的基本思想是:将整数按位数切割成不同的数字,然后按每个位数分别比较。
具体做法是:将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。
基数排序思想
1.求出数组中最大的元素。
2.求出最大元素是几位数。设为i位。
3.对所有的数进行i轮排序。首先排个位,然后在十位,然后百位…
4.每一轮的排位都需要分桶,桶是有顺序的,然后在把桶里的数按顺序放入原来的数组中。
5.直到i轮排序结束后,数组排序完成。
图文介绍
通过基数排序对数组{53, 3, 542, 748, 14, 214, 154, 63, 616},它的示意图如下:
在上图中,首先将所有待比较树脂统一为统一位数长度,接着从最低位开始,依次进行排序。
1. 按照个位数进行排序。
2. 按照十位数进行排序。
3. 按照百位数进行排序。
排序后,数列就变成了一个有序序列。
代码实现
#include<iostream>
using namespace std;
/*
* 获取数组a中最大值
*
* 参数说明:
* a -- 数组
* n -- 数组长度
*/
int getMax(int a[], int n)
{
int i, max;
max = a[0];
for (i = 1; i < n; i++)
if (a[i] > max)
max = a[i];
return max;
}
/*
* 对数组按照"某个位数"进行排序(桶排序)
*
* 参数说明:
* a -- 数组
* n -- 数组长度
* exp -- 指数。对数组a按照该指数进行排序。
*
* 例如,对于数组a={50, 3, 542, 745, 2014, 154, 63, 616};
* (01) 当exp=1表示按照"个位"对数组a进行排序
* (02) 当exp=10表示按照"十位"对数组a进行排序
* (03) 当exp=100表示按照"百位"对数组a进行排序
* ...
*/
void countSort(int a[], int n, int exp)
{
int output[n]; // 存储"被排序数据"的临时数组
int i, buckets[10] = {0};
// 将数据出现的次数存储在buckets[]中
for (i = 0; i < n; i++)
buckets[ (a[i]/exp)%10 ]++;
// 更改buckets[i]。目的是让更改后的buckets[i]的值,是该数据在output[]中的位置。
for (i = 1; i < 10; i++)
buckets[i] += buckets[i - 1];
// 将数据存储到临时数组output[]中
for (i = n - 1; i >= 0; i--)
{
output[buckets[ (a[i]/exp)%10 ] - 1] = a[i];
buckets[ (a[i]/exp)%10 ]--;
}
// 将排序好的数据赋值给a[]
for (i = 0; i < n; i++)
a[i] = output[i];
}
/*
* 基数排序
*
* 参数说明:
* a -- 数组
* n -- 数组长度
*/
void radixSort(int a[], int n)
{
int exp; // 指数。当对数组按各位进行排序时,exp=1;按十位进行排序时,exp=10;...
int max = getMax(a, n); // 数组a中的最大值
// 从个位开始,对数组a按"指数"进行排序
for (exp = 1; max/exp > 0; exp *= 10)
countSort(a, n, exp);
}
int main()
{
int i;
int a[] = {53, 3, 542, 748, 14, 214, 154, 63, 616};
int ilen = (sizeof(a)) / (sizeof(a[0]));
cout << "before sort:";
for (i=0; i<ilen; i++)
cout << a[i] << " ";
cout << endl;
radixSort(a, ilen); // 基数排序
cout << "after sort:";
for (i=0; i<ilen; i++)
cout << a[i] << " ";
cout << endl;
return 0;
}
堆排序
堆排序介绍
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。分为两种方法:
1.大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列;
2.小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列;
堆排序的平均时间复杂度为 Ο(nlogn)。
算法步骤
1.创建一个堆 H[0……n-1];
2.把堆首(最大值)和堆尾互换;
3.把堆的尺寸缩小 1,并调用 shift_down(0),目的是把新的数组顶端数据调整到相应位置;
4.重复步骤 2,直到堆的尺寸为 1。
动图演示:
步骤一:建立大根堆
① 无序序列建立完全二叉树
② 从最后一个叶子节点开始,从左到右,从下到上调整,将完全二叉树调整为大根堆
a.找到第1个非叶子节点6,由于6的右子节点9比6大,所以交换6和9。交换后,符合大根堆的结构。
c.找到第2个非叶子节点4,由于的4左子节点9比4大,所以交换4和9。交换后不符合大根堆的结构,继续从右到左,从下到上调整。
步骤二:交换堆元素(交换堆首和堆尾元素–获得最大元素)
步骤三:重建大根堆(前n-1个元素)
重复执行步骤二和步骤三,直到整个序列有序
代码实现
#include<iostream>
#include<vector>
using namespace std;
// 递归方式构建大根堆(len是arr的长度,index是第一个非叶子节点的下标)
void adjust(vector<int> &arr, int len, int index)
{
int left = 2*index + 1; // index的左子节点
int right = 2*index + 2;// index的右子节点
int maxIdx = index;
if(left<len && arr[left] > arr[maxIdx]) maxIdx = left;
if(right<len && arr[right] > arr[maxIdx]) maxIdx = right;
if(maxIdx != index)
{
swap(arr[maxIdx], arr[index]);
adjust(arr, len, maxIdx);
}
}
// 堆排序
void heapSort(vector<int> &arr, int size)
{
// 构建大根堆(从最后一个非叶子节点向上)
for(int i=size/2 - 1; i >= 0; i--)
{
adjust(arr, size, i);
}
// 调整大根堆
for(int i = size - 1; i >= 1; i--)
{
swap(arr[0], arr[i]); // 将当前最大的放置到数组末尾
adjust(arr, i, 0); // 将未完成排序的部分继续进行堆排序
}
}
int main()
{
vector<int> arr = {8, 1, 14, 3, 21, 5, 7, 10};
heapSort(arr, arr.size());
for(int i=0;i<arr.size();i++)
{
cout<<arr[i]<<endl;
}
return 0;
}
希尔排序
希尔排序介绍
希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。
希尔排序是基于插入排序的以下两点性质而提出改进方法的:
1.插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率;
2.但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位;
希尔排序的基本思想:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录"基本有序"时,再对全体记录进行依次直接插入排序。
算法步骤
1.选择一个增量序列 t1,t2,……,tk,其中 ti > tj, tk = 1;
2.按增量序列个数 k,对序列进行 k 趟排序;
3.每趟排序,根据对应的增量 ti,将待排序列分割成若干长度为 m 的子序列,分别对各子表进行直接插入排序。仅增量因子为 1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
动图演示:
代码实现
#include<iostream>
using namespace std;
void print(int a[], int n)
{
for(int j= 0; j<n; j++)
{
cout<<a[j] <<" ";
}
cout<<endl;
}
void shellSort(int a[], int n) //a -- 待排序的数组, n -- 数组的长度
{
int i,j,gap; // gap为步长,每次减为原来的一半。
for (gap = n / 2; gap > 0; gap /= 2)
{
// 共gap个组,对每一组都执行直接插入排序
for (i = 0 ;i < gap; i++)
{
for (j = i + gap; j < n; j += gap)
{
// 如果a[j] < a[j-gap],则寻找a[j]位置,并将后面数据的位置都后移。
if (a[j] < a[j - gap])
{
int tmp = a[j];
int k = j - gap;
while (k >= 0 && a[k] > tmp)
{
a[k + gap] = a[k];
k -= gap;
}
a[k + gap] = tmp;
}
}
}
}
}
int main()
{
int a[10] = {8,9,1,7,2,3,5,4,6,0};
cout<<"初始序列:";
print(a,10);
shellSort(a,10);
cout<<"排序结果:";
print(a,10);
system("pause");
}