c++经典排序算法总结
算法分类
一、冒泡排序
步骤
从头开始,每次比较两元素,若大者在前,则交换两元素,直至数组末尾,此时最大元素为数组最后的元素;
重复以上步骤,从头开始至上一轮比较的末尾元素;
性质
稳定算法;
适用场景
数据量量不大,对稳定性有要求,且数据基本有序的情况下
//冒泡排序
void BubbleSort(int* h,size_t len)
{
if(h==NULL)
{
return;
}
if(len<=1)
{
return;
}
for(int i=0;i<len-1;++i)
{
for(int j=0;j<len-1-i;++j)
{
if(h[j]>h[j+1])
swap(h[j],h[j+1]);
}
}
return
}
//时间复杂度:o(n*n) 空间复杂度o(1)
二、选择排序
步骤
搜索整个列表,找出最小项,若此项不为第1项,则与第1项交换位置;
重复上述步骤,每次搜索未被排序的剩余列表,并将最小元素与已排序段的后一位交换,直至列表所有元素均被排序;
性质
不稳定算法;
// 选择排序
void selectSort(vector<int>& array){
for (size_t i = 0; i < array.size(); i++){
size_t minIndex = i;
for (size_t j = i + 1; j < array.size(); j++){
if (array[minIndex] > array[j]){
minIndex = j;
}
}
if (minIndex != i){
swap(array[i], array[minIndex]);
}
}
}
时间复杂度O(n*n) 空间复杂度O(1)
插入排序
步骤
将第一个元素看作有序序列,后续元素当作无需序列,依次将无序序列元素插入有序序列当中;
性质
稳定算法;
for (int i = 1; i < nums.size(); ++i)
{
int temp = nums[i];
for (int j = i - 1; j >= 0; --j)
{
if (nums[j+1] < nums[j])
{
swap(nums[j], nums[j + 1]);
}
else
{
break;
}
}
}
return nums;
}
时间复杂度O(n*n) 空间复杂度O(1)
希尔排序
步骤
选择一个增量序列,初始增量gap=length/2,后续元素依次为前一元素除2,直至gap=1;
每轮以gap为步长,在列表上进行采样,将列表分为gap个小组,在每个小组内进行选择排序;
重复第二步,直至gap=1;
性质
不稳定算法;
int n = nums.size();
for (int gap = n / 2; gap >= 1; gap /= 2)
{
for (int i = gap; i < nums.size(); i ++)
{
//int tempValue = nums[i];
for (int j = i- gap; j >= 0; j -= gap)
{
if (nums[j+gap]<nums[j])
{
swap(nums[j + gap], nums[j]);
}
}
}
}
时间复杂度 O(nlogn),空间复杂度O(1)
并归排序
步骤
将列表从正中间分为两个子列表;
按照第一步,递归拆分每个子列表,直至子列表最大长度为1;
按照拆分层级,依次按大小合并各子列表,直至全部合并完成。
性质
稳定算法;
void mergeSortHelper(vector<int> &nums, vector<int> ©Nums, int left, int right)
{
if (left < right)
{
int mid = (left + right) / 2;
mergeSortHelper(nums, copyNums, left, mid);
mergeSortHelper(nums, copyNums, mid + 1, right);
mergeSort(nums, copyNums, left, right);
}
}
void mergeSort(vector<int> &nums, vector<int> ©Nums, int left, int right)
{
int mid = (left + right) / 2;
int i = left, j = mid + 1, k = 0;
while (i <= mid || j <= right)
{
if (i > mid)
{
copyNums[k] = nums[j];
j++;
}
else if (j > right)
{
copyNums[k] = nums[i];
i++;
}
else if(nums[i]>nums[j])
{
copyNums[k] = nums[j];
j++;
}
else
{
copyNums[k] = nums[i];
i++;
}
k++;
}
for (int i = left; i <= right; ++i)
{
nums[i] = copyNums[i - left];
}
}
时间复杂度O(nlogn) 空间复杂度O(1)
快速排序
步骤
从列表中选出一个元素,作为“基准”pivot,基准一般随机选择,或采用最左端、最右端和中间位置3元素的中值;
将小于基准的元素排在基准前面,大于基准的元素排在基准后面,此时基准元素所在位置即为其最终排序完成时的位置;
以基准元素为界,将列表分为两个子列表;
递归地对子列表重复上述操作。
性质
不稳定算法;
当数据量很小(N<=20)时,快速排序效果不如插入排序,因为快速排序不稳定且有递归开销;
适用场景
常用于查找一组中前k大的数据
//快递排序
int medianPovit(vector<int> &nums, int left, int mid, int right)
{
if (nums[left] > nums[mid])
{
swap(nums[left], nums[mid]);
}
if (nums[left] > nums[right]) {
swap(nums[left], nums[right]);
}
if (nums[mid] > nums[right]) {
swap(nums[mid], nums[right]);
}
return nums[mid];
}
int partition(vector<int> &nums, int left, int right)
{
int meida = (left + right) / 2;
int pivotkey = medianPovit(nums,left, meida, right);
swap(nums[meida], nums[right]);
int j = left-1;
for (int i = left; i <right; ++i)
{
if (nums[i] < pivotkey)
{
j++;
if (j != i)
{
swap(nums[i], nums[j]);
}
}
}
j++;
swap(nums[j], nums[right]);
return j;
}
void quickSort(vector<int> &nums, int left, int right)
{
if (left < right)
{
int pivot = partition(nums, left, right);
quickSort(nums, left, pivot - 1);
quickSort(nums, pivot + 1, right);
}
}
时间复杂度:最好O(nlogn) 最坏O(n*n) 平均O(nlogn)
空间复杂度:最好O(logn) 最坏O(n) 平均O(logn)
// 快速排序 非递归(迭代版)
void quickSortIteration(vector<int>& array) {
stack<vector<int>> boundaries;
int left = 0, right = array.size() - 1;
while (left < right || !boundaries.empty()) {
if (left >= right) {
vector<int> boundary = boundaries.top();
boundaries.pop();
left = boundary[0];
right = boundary[1];
}
int pivotLoction = partition(array, left, right);
if (pivotLoction + 1 < right) {
boundaries.push({ pivotLoction + 1, right });
}
right = pivotLoction - 1;
}
}
堆排序
步骤
将数字转化为一个堆;
堆是具有以下两属性的二叉树:
(1)每个节点的值大于等于其子节点的值;
(2)树完全平衡,即最底层叶子节点都位于左侧(完全),且左右子树高度相差不超过1(平衡);
堆也被称为优先队列,具有先进先出的特性,在堆底插入元素,在堆顶取出元素。
取出堆顶元素(最大元素),作为有序数数组末尾元素,并对二叉树进行调整使其满足堆的特性;
重复上一步骤,依次取出堆顶元素,并插入到有序数组中,上一插入元素之前的位置,直到堆空为止;
性质
不稳定算法;
适用范围
不适合待排序序列个数较少的情况
void Heapsort(vector<int> &nums)
{
for (int i = nums.size() / 2 - 1; i >= 0; --i)
{
HeapAdjust(nums, i, nums.size() - 1);
}
for (int i = nums.size() - 1; i >= 1; --i)
{
swap(nums[0], nums[i]);
HeapAdjust(nums, 0, i - 1);
}
}
建堆时间为O(n)
时间复杂度:最好、最坏、平均均为(nlogn)
空间复杂度O(1)
堆的插入和删除时间复杂度均为lon(n)
//堆排序;
void HeapAdjust(vector<int> &nums, int i, int length)
{
int temp = nums[i];
for (int j = i * 2 + 1; j <= length; j = j * 2 + 1)
{
if (j + 1 <= length&&nums[j] < nums[j + 1])
{
j++;
}
if (nums[j] < temp)
{
break;
}
nums[i] = nums[j];
i = j;
}
nums[i] = temp;
}
计数排序
步骤
遍历待排序数组A,找出其最小值min和最大值max;
创建一个长度为max-min+1的数组B,其所有元素初始化为0,数组首位对应数组A的min元素,索引为i位置对应A中值为min+i的元素;
遍历数组A,在B中对应位置记录A中各元素出现的次数;
遍历数组B,按照之前记录的出现次数,输出几次对应元素;
性质
稳定排序算法;
外部排序;
//计数排序
int min = nums[0], max = nums[0];
for (int i = 0; i < nums.size(); ++i)
{
if (min > nums[i])
{
min = nums[i];
}
if (max < nums[i])
{
max = nums[i];
}
}
vector<int> counts(max - min + 1);
for (int i = 0; i < nums.size(); ++i)
{
counts[nums[i] - min]++;
}
int index = 0;
for (int i = 0; i < counts.size(); ++i)
{
int n = counts[i];
while (n>0)
{
nums[index] = i + min;
index++;
n--;
}
}
return nums;
时间复杂度O(n+k) 空间复杂度O(k)
桶排序
步骤
设置固定数量的空桶;
找出待排序数组的最大值和最小值;
根据最大最小值平均划分各桶对应的范围,并将待排序数组放入对应桶中;
为每个不为空的桶中数据进行排序(例如,插入排序);
拼接不为空的桶中数据,得到排序后的结果。
特性
稳定算法;
常见排序算法中最快的一种;
适用于小范围(最大值和最小值差值较小),独立均匀分布的数据;
可以计算大批量数据,符合线性期望时间;
外部排序方式,需额外耗费n个空间;
// 桶排序
void bucketSort (vector<int>& array, int bucketCount){
if (array.empty()){
return;
}
// 找出最大最小值
int max = array.front(), min = array.front();
for (int i = 1; i < array.size(); i++){
if (min > array[i]){
min = array[i];
}
else if (max < array[i]){
max = array[i];
}
}
// 将待排序的各元素分入对应桶中
vector<vector<int>> buckets(bucketCount);
int bucketSize = ceil((double)(max - min + 1) / bucketCount);
for (int i = 0; i < array.size(); i++){
int bucketIndex = (array[i] - min) / bucketSize;
buckets[bucketIndex].push_back(array[i]);
}
// 对各桶中元素进行选择排序
int index = 0;
for (vector<int> bucket : buckets){
if (!bucket.empty()){
// 使用选择排序算法对桶内元素进行排序
selectSort(bucket);
for (int value : bucket){
array[index] = value;
index++;
}
}
}
}
// 桶排序
void bucketSort (vector<int>& array){
bucketSort (array, array.size() / 2);
}
时间复杂度O(n+k) 空间复杂度O(n+k)
基数排序
步骤
将各待比较元素数值统一数位长度,即对数位短者在前补零;
根据个位数值大小,对数组进行排序;
重复上一步骤,依次根据更高位数值进行排序,直至到达最高位;
特性
稳定算法;
适用于正整数数据(若包含负数,那么需要额外分开处理);
对于实数,需指定精度,才可使用此算法。