无论我们平时是自己写工程代码,还是刷各种求职宝典的题目,亦或是临场面试的时候,我们可能都逃脱不了要写排序算法,那么这里我将对平时常用的几种排序算法做一个总结,分享给大家。
(注:以下排序过程均以从小到大的顺序,代码实现均用C++)
1. 选择排序
基本思想:给定一个序列,从中找到最小的一个放到序列的第一个位置,再从剩下的n-1个中找到最小的放到第二个位置……直到剩下最后一个元素就直接放在最后为止,排序结束。
代码实现:
// SelectSort.cpp
void SelectSort(int arr[], int size)
{
for(int i = 0; i < size-1; ++i)
{
int temp = arr[i];
int pos = i;
for(int j = i+1; j < size; ++j)
{
if(arr[j] < temp)
{
pos = j;
temp = arr[j];
}
}
arr[pos] = arr[i];
arr[i] = temp;
}
}
分析:从选择排序的思想可以看出,寻找最小的元素需要一个循环过程,而排序又是需要一个循环的过程。因此,显而易见,此算法的时间复杂度是O(n*n),随着n的增大,算法的效率会降低。这是一种不稳定的排序方法。
2. 冒泡排序
基本思想:将n个记录看做纵向排列,每趟排列时自下至上对每对儿相邻记录进行比较,若次序不符合要求就交换。每趟排序结束时都能使排序范围内关键字最小的记录像一个气泡一样升到表上端对应的位置,整个排序过程共进行n-1趟。
代码实现:
//BubbleSort.cpp
void BubbleSort(int arr[], int size)
{
bool flag = false;//判断在某一次循环中是否发生过顺序交换
for(int i = 0; i < size-1; ++i)
{
int temp;
//这里j>i+1,因为对于前面i个已经排序好的数不用再比较了
for(int j = n-1; j >= i+1; ++j)
{
if(arr[j] < arr[j-1])
{
temp = arr[j];
arr[j] = arr[i];
arr[i] = temp;
flag = true;
}
}
if(!flag)
break;// 某次循环没有发生交换的话,说明序列已经是顺序的
}
}
分析:冒泡排序的时间复杂度也较高,达到O(n*n),每次遍历无序区间都将最小元素移至最前端。该排序算法是一种稳定的排序方式。
3. 插入排序
基本思想:将前面的区间视作有序区间,然后将有序区间的后一元素插入到有序区间的适当位置。直至有序区间扩展到原始区间大小,排序结束。
代码实现:
// InsertSort.cpp
void InserSort(int arr[], int size)
{
int i, j, temp;
for(i = 1; i < size; ++i)
{
for(j = i; j > 0; --j)
{
if(arr[j] < arr[j-1])
{
temp = arr[j];
arr[j] = arr[j-1];
arr[j-1] = temp;
}
else
//当不再小于前面的某一个数时,说明已经插入到正确位置
break;
}
}
}
分析:插入排序的时间复杂度达到0(n*n),排序的运行时间和待排元素的原始排列顺序紧密相关。该排序算法是一种稳定的排序方式。
4. 快速排序
基本思想:找出一个元素作为基准,然后对数组进行分区操作,使基准左边的元素值都不大于基准值,基准右边的值都不小于基准值,如此作为基准的元素调整到排序后的正确位置。递归快速排序,将其他n-1个元素也调整到排序后的正确位置。最后每个元素都是在排序后的正确位置,排序完成。所以快速排序的核心是分区操作,即如何调整基准的位置以及调整返回基准的最终位置分治递归。
代码实现:
// QuickSort
void QuickSort(int arr[], int left, int right)
{
if(left > right)
return;
int key = arr[left]; // 选择一个基准
int i = left;
int j = right;
while(i < j)
{
//先找右边小于key的
while(i < j && arr[j] >= key)
j--;
if(i <j)
arr[i++] = a[j];//填左边的坑,此时相当于在j处挖坑
//再找左边大于key的
while(i < j && arr[i] < key)
i++;
if(i < j)
arr[j--] = arr[i];//填j处的坑,并在i处挖坑
}
//填补i处的坑
arr[i] = key;
QuickSort(arr, left, i-1);//迭代i左边的子序列
QuickSort(arr, i+1, right);//迭代i右边的子序列
}
分析:快速排序的时间主要耗费在划分操作上,对长度为k的区间进行划分,共需k-1次关键字的比较。
最坏情况是每次划分选取的基准都是当前无序区中关键字最小(或最大)的记录,划分的结果是基准左边的子区间为空(或右边的子区间为空),而划分所得的另一个非空的子区间中记录数目,仅仅比划分前的无序区中记录个数减少一个。时间复杂度为O(n*n)。
在最好情况下,每次划分所取的基准都是当前无序区的”中值”记录,划分的结果是基准的左、右两个无序子区间的长度大致相等。总的关键字比较次数:O(nlgn)。
尽管快速排序的最坏时间为O(n2),但就平均性能而言,它是基于关键字比较的内部排序算法中速度最快者,快速排序亦因此而得名。它的平均时间复杂度为O(nlgn)。
5.堆排序
/*
arr[start+1...end]满足最大堆的定义,
将arr[start]加入到最大堆arr[start+1...end]中,
调整arr[start]的位置,使arr[start...end]再次成为最大堆
注:由于数组从0开始计算序号,也就是二叉堆的根结点序号位0,
因此序号为i的左右子结点的序号分别为2i+1和2i+2
*/
//调整堆
void HeapAdjustDown(int* arr, int start, int end)
{
int temp = arr[start]; //保存当前结点
int i = 2 * start + 1; //该节点的左孩子在数组中的位置序号
while(i <= end)
{
//找出左右孩子中最大的那个
if(i+1 <= end && arr[i+1] > arr[i])
i++;
//如果符合堆的定义,则不用调整位置
if(arr[i] <= temp)
break;
//最大的子节点向上移动,替换掉其父结点
arr[start] = arr[i];
start = i;
i = 2 * start + 1;
}
arr[start] = temp;
}
/*
堆排序后的顺序为从小到大
因此需要建立最大堆
*/
void HeapSort(int* arr, int len)
{
int i;
//把数组建成最大堆
//第一个非叶子节点的位置序号为len/2-1
for(i = len/2-1; i >= 0; i--)
HeapAdjust(arr, i, len-1);
//进行堆排序
for(i = len-1; i > 0; i--)
{
//堆顶元素和最后一个元素交换位置,
//这样最后的一个位置保存的是最大的数,
//每次循环依次将最大的数值在放进其前面一个位置,
//这样得到的顺序就是从小到大
int temp = arr[i];
arr[i] = arr[0];
arr[0] = temp;
//将arr[0...i-1]重新调整为最大堆
HeapAdjust(arr, 0, i-1);
}
}
6.归并排序
void MergeArray(int* arr, int start, int mid, int end, int* result)
{
int i = start;
int j = mid+1;
int k = 0;
while(i <= mid && j <= end)
{
if(arr[i] <= arr[j])
result[k++] = arr[i++];
else
result[k++] = arr[j++];
}
while(i <= mid)
result[k++] = arr[i++];
while(j <= end)
result[k++] = arr[j++];
for(i = 0; i < k; ++i)
arr[start+i] = result[i];
}
void MergeSort(int* arr, int start, int end, int* result)
{
if(start < end)
{
int mid = (start+end) / 2;
MergeSort(arr, start, mid, result);//对左区间进行排序
MergeSort(arr, mid+1, end, result);//对右区间进行排序
MergeArray(arr, start, mid, end, result);//对两个子区间进行合并
}
}
7.希尔排序
希尔排序实质上就是步长不定的插入排序。
步长从n/2开始,并且总是以1结束。
void(int* arr, int n)
{
int gap;
for(gap = n/2; gap > 0; gap /= 2)
{
for(int i = gap; i < n; ++i)
{
//gap后的元素逐次向前比较
int k = i;
while(k-gap >= 0 && arr[k] < arr[k-gap])
{
int temp = arr[k];
arr[k] = arr[k-gap];
arr[k-gap] = temp;
k -= gap;
}
}
}
}
8.基数排序(桶排序)
/********************************************************
*函数名称:GetNumInPos
*参数说明:num 一个整形数据
* pos 表示要获得的整形的第pos位数据
*说明: 找到num的从低到高的第pos位的数据
*********************************************************/
int GetNumInPos(int num,int pos)
{
int temp = 1;
for (int i = 0; i < pos - 1; i++)
temp *= 10;
return (num / temp) % 10;
}
/********************************************************
*函数名称:RadixSort
*参数说明:pDataArray 无序数组;
* iDataNum为无序数据个数
*说明: 基数排序
*********************************************************/
#define RADIX_10 10 //整形排序
#define KEYNUM_31 10 //关键字个数,这里为整形位数
void RadixSort(int* pDataArray, int iDataNum)
{
int *radixArrays[RADIX_10]; //分别为0~9的序列空间
for (int i = 0; i < 10; i++)
{
radixArrays[i] = (int *)malloc(sizeof(int) * (iDataNum + 1));
radixArrays[i][0] = 0; //index为0处记录这组数据的个数
}
for (int pos = 1; pos <= KEYNUM_31; pos++) //从个位开始到31位
{
for (int i = 0; i < iDataNum; i++) //分配过程
{
int num = GetNumInPos(pDataArray[i], pos);
int index = ++radixArrays[num][0];
radixArrays[num][index] = pDataArray[i];
}
for (int i = 0, j =0; i < RADIX_10; i++) //收集
{
for (int k = 1; k <= radixArrays[i][0]; k++)
pDataArray[j++] = radixArrays[i][k];
radixArrays[i][0] = 0; //复位
}
}
}