排序算法 | 平均时间复杂度 | 最好情况 | 最坏情况 | 空间复杂度 | 排序方式 | 稳定性 |
---|---|---|---|---|---|---|
冒泡排序 | O(n^2) | O(n) | O(n^2) | O(1) | In-place | 稳定 |
选择排序 | O(n^2) | O(n^2) | O(n^2) | O(1) | In-place | 不稳定 |
插入排序 | O(n^2) | O(n) | O(n^2) | O(1) | In-place | 稳定 |
希尔排序 | O(nlog(n)) | O(nlog^2(n)) | O(nlog^2(n)) | O(1) | In-place | 不稳定 |
归并排序 | O(nlog(n)) | O(nlog(n)) | O(nlog(n)) | O(n) | Out-place | 稳定 |
快速排序 | O(nlog(n)) | O(nlog(n)) | O(n^2) | O(log(n)) | In-place | 不稳定 |
堆排序 | O(nlog(n)) | O(nlog(n)) | O(nlog(n)) | O(1) | In-place | 不稳定 |
计数排序 | O(n+k) | O(n+k) | O(n+k) | O(k) | Out-place | 稳定 |
桶排序 | O(n+k) | O(n+k) | O(n^2) | O(n+k) | Out-place | 稳定 |
基数排序 | O(n*k) | O(n*k) | O(n*k) | O(n+k) | Out-place | 稳定 |
1. 冒泡排序
- 原理:从序列头部开始遍历,两两比较,如果前者比后者大,则交换位置,直到最后将最大的数(本次排序最大的数)交换到无序序列的尾部,从而成为有序序列的一部分。
- 步骤:
- 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
- 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
- 针对所有的元素重复以上的步骤,除了最后一个。
- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
- 程序:
void bubbleSort(vector<int> &arr)
{
int tempVal=0;
for (int i = 0; i < arr.size(); i++)//遍历所有元素
{
for (int j = 0; j < arr.size() - i - 1; j++)//逐次比较交换元素,前面交换到最后的元素不再比较
{
if (arr[j] > arr[j + 1])
{
tempVal = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tempVal;
}
}
}
}
2. 选择排序
- 原理:它的工作原理是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。
- 步骤:
- 初始状态:无序区为 R[1…n],有序区为空。
- 第 1 趟排序:在无序区 R[1…n]中选出关键字最小的记录 R[k],将它与无序区的第 1 个记录 R[1]交换,使 R[1…1]和 R[2…n]分别变为记录个数增加 1 个的新有序区和记录个数减少 1 个的新无序区。
- ……
- 第 i 趟排序:第 i 趟排序开始时,当前有序区和无序区分别为 R[1…i-1]和 R(i…n)。该趟排序从当前无序区中选出关键字最小的记录 R[k],将它与无序区的第 1 个记录 R 交换,使 R[1…i]和 R 分别变为记录个数增加 1 个的新有序区和记录个数减少 1 个的新无序区。
- 程序:
void selectionSort(vector<int> &arr)
{
int tempVal = 0, minIndex = 0;
for (int i = 0; i < arr.size()-1; i++)
{
minIndex = i;
/*寻找最小数*/
for (int j = i+1; j < arr.size(); j++)
{
if (arr[minIndex] > arr[j])//如果第 j 个数比第 minIndex 个数小
{
minIndex = j;//记录第 j 个数为当次循环最小数
}
}
//交换第 i 个数与最小数
tempVal = arr[minIndex];
arr[minIndex] = arr[i];
arr[i] = tempVal;
}
}
3. 插入排序
- 原理:基本思路是先将待排序序列的第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列;然后从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置,直到所有数据都完成排序;如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。
- 步骤:
- 从第一个元素开始,该元素可以认为已经被排序。
- 取出下一个元素,在已经排序的元素序列中从后向前扫描。
- 如果该元素(已排序)大于新元素,将该元素移到下一位置。
- 重复步骤 3,直到找到已排序的元素小于或者等于新元素的位置。
- 将新元素插入到该位置后。
- 重复步骤 2~5。
- 程序:
void insterSort(vector<int>& arr)
{
int tempVal=0;
int j = 0;
for (int i = 1; i < arr.size(); i++)
{
j = i;
tempVal = arr[i];//记录待插入元素
for (j = i - 1; (j >= 0) && (tempVal < arr[j]); j--)//如果大于待插入元素,此元素一直后移,为待插入元素腾出位置
{
arr[j + 1] = arr[j];
}
arr[j + 1] = tempVal;
}
}
4. 希尔排序
- 原理:希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至 1 时,整个文件恰被分成一组,算法便终止。
- 步骤:
- 先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,具体算法描述:选择一个增量序列 t1,t2,…,tk,其中 ti>tj,tk=1;
- 按增量序列个数 k,对序列进行 k 趟排序;
- 每趟排序,根据对应的增量 ti,将待排序列分割成若干长度为 m 的子序列,分别对各子表进行直接插入排序。
- 仅增量因子为 1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
- 程序:
//在插入排序基础上, 希尔排序令jump=1得插入排序
void shellSort(vector<int> &arr)
{
int jump = 0, j = 0;
int tempVal = 0;
jump = arr.size() >> 2;
while (jump!=0)
{
for (int i = jump; i < arr.size(); i ++)
{
tempVal = arr[i];//记录待插入得值
//元素依次后移,为tempVal腾出空间
for (j = i - jump;(j >=0)&&(tempVal < arr[j]);j -= jump)
{
arr[j + jump] = arr[j];
}
arr[j + jump] = tempVal;
}
jump = jump >> 1;//取半步长
}
}
5. 归并排序
- 原理:将一个数组分为两部分,然后对左边归并排序,对右边归并排序,再将两个有序子数组合并即可。
- 步骤:
- 把长度为 n 的输入序列分成两个长度为 n/2 的子序列;
- 对这两个子序列分别采用归并排序;
- 将两个排序好的子序列合并成一个最终的排序序列。
- 程序:
void merge_in_arr(vector<int>& arr, int left, int mid, int right)
{
int length = right - left + 1; //定义一个辅助的空间的长度
int* pData = new int[sizeof(int) * length];
memset(pData, 0, sizeof(int) * length); //初始化内存
int low = left; //左边区间的起始下标
int high = mid + 1; //右边区间的起始下标
int index = 0; //辅助数组的下标
while (high <= right) //右区间没有合并完
{
while ((low <= mid)&& arr[low] <= arr[high])//证明左区间没有合并完,且左区间的值小于右区间的值
{
pData[index] = arr[low]; //把左边的值放进辅助数组
low++; //左边往高位移,下一次需要判断左边的新下标
index++; //下一次放进辅助数组的新下标
}
if (low > mid)break; //证明左区间已经放完
while ((high <= right)&& arr[low] > arr[high])//证明右区间没有合并完,且左区间的值大于右区间的值
{
pData[index] = arr[high]; //把右边的值放进辅助数组
high++; //右边往高位移,下一次需要判断右边的新下标
index++; //下一次放进辅助数组的新下标
}
}
if (high <= right)
memmove(&pData[index], &arr[high], sizeof(int) * (right - high + 1));
if (low <= mid)
memmove(&pData[index], &arr[low], sizeof(int) * (mid - low + 1));
memmove(&arr[left], pData, sizeof(int) * length);
delete pData; //释放空间
}
void mergeSort(vector<int> &arr, int left, int right)
{
if (left >= right)//递归的终止条件,left == right证明这个区间只有一个元素,不需要再拆了
return;
int mid = ((right - left) >> 1) + left;//求中点
mergeSort(arr, left, mid); //拆分左
mergeSort(arr, mid + 1, right); //拆分右
merge_in_arr(arr,left,mid,right);//合并
}
6. 快速排序
- 原理:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
- 步骤:
- 从数列中挑出一个元素,称为 “基准”(pivot);
- 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。
- 在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
- 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
- 程序:
void quickSort(vector<int>& arr,int low,int high)
{
if (low >= high)
{
return;
}
int left = low;
int right = high;
int key = arr[low];//选择左边第一个数作为集准
while (right > left)
{
/*
key右边的数大于key
key左边的数小于key
*/
while ((right > left) && (arr[right] >= key))//从右边开始寻找第一个小于key的数
{
right--;
}
arr[left] = arr[right];//然后存到左边第一个数,之后right不变
while ((right > left) && (arr[left] <= key))//从左边开始寻找第一个大于key的数
{
left++;
}
arr[right] = arr[left];//然后将其存到右边第一个数
}
arr[left] = key;
quickSort(arr,low,left-1);
quickSort(arr, right+1,high);
}
7. 堆排序
- 原理:堆排序就是把最大堆堆顶的最大数取出,将剩余的堆继续调整为最大堆,再次将堆顶的最大数取出,这个过程持续到剩余数只有一个时结束。
- 步骤:
- 最大堆调整(Max-Heapify):将堆的末端子节点作调整,使得子节点永远小于父节点;
- 创建最大堆(Build-Max-Heap):将堆所有数据重新排序,使其成为最大堆;
- 堆排序(Heap-Sort):移除位在第一个数据的根节点,并做最大堆调整的递归运算 继续进行下面的讨论前,需要注意的一个问题是:数组都是 Zero-Based,这就意味着我们的堆数据结构模型要发生改变。
- 程序:
static int len;
void buildMaxHeap(vector<int>& array);
void adjustHeap(vector<int>& array, int i);
void heapSort(vector<int>& array) {
len = array.size();
if (len < 1) return;
//1.构建一个最大堆
buildMaxHeap(array);
//2.循环将堆首位(最大值)与末位交换,然后在重新调整最大堆
while (len > 0) {
Swap(array, 0, len - 1);
len--;
adjustHeap(array, 0);
}
return ;
}
/* 建立最大堆*/
void buildMaxHeap(vector<int>& array) {
//从最后一个非叶子节点开始向上构造最大堆
for (int i = (len / 2 - 1); i >= 0; i--)
{
adjustHeap(array, i);
}
}
/*调整使之成为最大堆*/
void adjustHeap(vector<int>& array, int i) {
int maxIndex = i;
//如果有左子树,且左子树大于父节点,则将最大指针指向左子树
if (i * 2 < len && array[i * 2] > array[maxIndex])
maxIndex = i * 2;
//如果有右子树,且右子树大于父节点,则将最大指针指向右子树
if (i * 2 + 1 < len && array[i * 2 + 1] > array[maxIndex])
maxIndex = i * 2 + 1;
//如果父节点不是最大值,则将父节点与最大值交换,并且递归调整与父节点交换的位置。
if (maxIndex != i) {
Swap(array, maxIndex, i);
adjustHeap(array, maxIndex);
}
}
8. 计数排序
- 原理:计数排序使用一个额外的数组 C,其中第 i 个元素是待排序数组 A 中值等于 i 的元素的个数。然后根据数组 C 来将 A 中的元素排到正确的位置。
- 步骤:
- 找出待排序的数组中最大和最小的元素;
- 统计数组中每个值为 i 的元素出现的次数,存入数组 C 的第 i 项;
- 对所有的计数累加(从 C 中的第一个元素开始,每一项和前一项相加);
- 反向填充目标数组:将每个元素 i 放在新数组的第 C(i)项,每放一个元素就将 C(i)减去 1。
- 程序:
void countingSort(vector<int>& array)
{
int bias=0, min = 0, max = 0;
if (array.size() == 0) return ;
min = array[0], max = array[0];
/*遍历一遍找出最大值和最小值*/
for (int i = 1; i < array.size(); i++) {
if (array[i] > max)
max = array[i];
if (array[i] < min)
min = array[i];
}
bias = 0 - min;
/*创建内存,并初始化为0,用于计数*/
int *bucket = new int[max - min + 1];
memset(bucket,0, max - min + 1);
/*计数出现的次数*/
for (int i = 0; i < array.size(); i++) {
bucket[array[i] + bias]++;
}
/*排序*/
int index = 0, i = 0;
while (index < array.size()) //直到重新填充整个array
{
if (bucket[i] != 0)
{
array[index] = i - bias;
bucket[i]--;
index++;
}
else
{
i++;
}
}
delete bucket;
return ;
}
9. 桶排序
- 原理:将数组分到有限数量的桶子里,每个桶子再个别排序。
- 步骤:
- 设定合适数量的分桶;
- 遍历待排序序列,将每个元素放入到对应的桶中;
- 对每个非空桶进行合适的排序算法;
- 按顺序访问桶,将桶中的元素依次放回到原序列中对应的位置。
- 程序:
void bucketSort(vector<int>& array, int bucketSize)
{
if (array.size() == 0 || array.size() < 2)
return ;
int max = array[0], min = array[0];
// 找到最大值最小值
for (int i = 0; i < array.size(); i++)
{
if (array[i] > max)
max = array[i];
if (array[i] < min)
min = array[i];
}
int bucketCount = (max - min) / bucketSize + 1;//桶的数量
vector<vector<int>> bucket(bucketCount);
for (int i = 0; i < array.size(); i++)//分配数据到每个桶
{
bucket[(array[i] - min) / bucketSize].push_back(array[i]);
}
for (int i = 0; i < bucketCount; i++)
{
//使用其他排序算法,对每个桶进行排序
shellSort(bucket[i]);
}
/*显示每个桶的内容,桶为空-不显示*/
for (int i = 0; i < bucketCount; i++)
{
if (bucket[i].size() != 0)
{
cout << i << "号桶: [";
for (int j = 0; j < bucket[i].size() - 1; j++)
{
cout << bucket[i][j] << ",";
}
cout << bucket[i][bucket[i].size() - 1] << "]" << endl;
}
}
int index1 = 0, index2 = 0;
for (int i = 0; i < array.size(); i++)//读取每个桶的数据
{
if (bucket[index1].size() != 0)
{
array[i] = bucket[index1][index2];
index2++;
if (index2 == bucket[index1].size())
{
index2 = 0;
index1++;
}
}
else
{
index1++;
}
}
return ;
}
10. 基数排序
- 原理:将整数按位数切割成不同的数字,然后按每个位数分别比较。
- 步骤:
- 取得数组中的最大数,并取得位数;
- arr 为原始数组,从最低位开始取每个位组成 radix 数组;
- 对 radix 进行计数排序(利用计数排序适用于小范围数的特点)。
- 程序:
void radixSort(vector<int>& array)
{
if (array.size() < 2)
return;
// 先算出最大数的位数;
int max = array[0];
for (int i = 1; i < array.size(); i++)
{
if (array[i] > max)max = array[i];
}
int maxDigit = 0;
while (max != 0) //计算位数
{
max /= 10;
maxDigit++;
}
int mod = 10, div = 1;
vector<vector<int>> radixarr(10);//创建10个桶
for (int i = 0; i < maxDigit; i++, mod *= 10, div *= 10)
{
for (int j = 0; j < array.size(); j++) //遍历一次,将所有数据分配到10个桶
{
int num = (array[j] % mod) / div;
radixarr[num].push_back(array[j]);
}
int index = 0;
for (int j = 0; j < radixarr.size(); j++)
{
for (int k = 0; k < radixarr[j].size(); k++)
array[index++] = radixarr[j][k];
radixarr[j].clear();
}
}
return ;
}