目录
算法名称 | 时间复杂度 | 应用场景 |
冒泡排序 | O(n^2) | 在最好的情况下,也就是数列本身是排好序的,需要进行 n - 1 次比较 |
快速排序 | O(nlogn) | 一种不稳定的排序算法 |
插入排序 | O(n^2) | 最稳定的排序方法 |
堆排序 | O(nlogn) | 不适用于序列个数较少的情况 |
归并排序 | O(nlogn) | 是一种效率高且稳定的算法 |
希尔排序 | O(n^(3/2)) | 时间复杂度相对O(n^2)较低 |
简单选择排序 | O(n^2) | 性能上略优于冒泡排序 |
基数排序 | O(KN) | 在某些时候,基数排序法的效率高于其它的稳定性排序法 |
一、冒泡排序
冒泡排序的原理:每一趟只能确定将一个数归位。即第一趟只能确定将末位上的数归位,第二趟只能将倒数第 2 位上的数归位,依次类推下去。如果有 n 个数进行排序,只需将 n-1 个数归位,也就是要进行 n-1 趟操作。
而 “每一趟 ” 都需要从第一位开始进行相邻的两个数的比较,将较大的数放后面,比较完毕之后向后挪一位继续比较下面两个相邻的两个数大小关系,重复此步骤,直到最后一个还没归位的数。
#include<iostream>
#include<vector>
using namespace std;
/*冒泡排序*/
class bubbleSort {
private:
vector<int> nums;
public:
bubbleSort(vector<int> temp) :nums(temp) {};
void sort();
void show();
};
//排序
void bubbleSort::sort()
{
for (int i = 0; i < nums.size(); i++)
{
for (int j = 1; j < nums.size() - i; j++)
{
if (nums[j - 1] > nums[j])
swap(nums[j - 1], nums[j]);
}
}
}
//打印排序后的数组
void bubbleSort::show()
{
for (int i = 0; i < nums.size(); i++)
cout << nums[i] << " ";
cout << endl;
}
二、快速排序
快速排序的原理:
单趟排序的思路:取区间中最左(或最右边)的元素为key,定义两个变量,这里假设是p和q,q从区间的最右边向左走,找到比key小的元素就停下。p从最左边向右走,找到比key大的元素就停下。然后交换p和q所指向的元素。
重复上面的过程,直到pq相遇,交换key和pq相遇位置的元素。
将key左右两边的区间分别进行上述单趟排序,并以此规律进行递归,截取更小区间进行单趟排序,最终当区间长度为1时,退出当前层,回溯,排序结束。
#include<iostream>
#include<vector>
using namespace std;
/*快速排序*/
class fastSort {
private:
vector<int> nums;
public:
fastSort(vector<int>temp) :nums(temp) {};
void sort(int start, int end);
void show();
};
void fastSort::sort(int start,int end)
{
if (end - start <= 0)
return;
int left = start;//【注意!!!】此处不是left=start+1; 而是left=start;
int right = end;
while (right > left)
{
//如果每次比较的值取的是最左边的第一个数,那么在进行比较分配左右子数组时,应该是右数组先开始,反之亦然。
while (nums[right] > nums[start] && right > left)
right--;
while (nums[left] <= nums[start] && right > left)
left++;
if (left == right)
{
//当left和right重合时,将最左边第一个数与重合点进行交换
swap(nums[start], nums[left]);
break;
}
else
{ //当右边出现小于左边第一个数的元素,左边出现大于左边第一个元素的数,则交换这两个数的位置。
swap(nums[left], nums[right]);
}
}
//左递归区间,单趟排序
sort(start, left - 1);
//左递归区间,单趟排序
sort(left + 1, end);
}
//打印排序后的数组
void fastSort::show()
{
for (int i = 0; i < nums.size(); i++)
cout << nums[i] << " ";
cout << endl;
}
三、插入排序
快速排序的原理:
从第二个数开始遍历,for(int cur=1;cur<nums.size();cur++),当遍历到当前元素nums[cur]时,倒序向前[0:cur]区间内取遍历寻找,寻找到一个小于nums[cur]的元素索引insert,将nums[cur]插入到insert+1位置。
注意:此处是插入,原[index+1:cur-1]区间的元素都向后移一位,为index提供空间,不是交换。
#include<iostream>
#include<vector>
using namespace std;
/*插入排序*/
class insertSort {
private:
vector<int> nums;
public:
insertSort(vector<int> temp) :nums(temp) {};
void sort();
void makRoom(int index, int num);
void show();
};
void insertSort::sort()
{
//从第二个元素开始,第一个元素就认为是已经排好的数组
for (int i = 1; i < nums.size(); i++)
{
for (int j = i - 1; j >= 0; j--)
{
//寻找排列好的数组中从大到小的第一个小于num[i]的元素nums[j],将nums[i]插入到j+1位置
if (nums[i] >= nums[j])
{
makRoom(j + 1, i);
break;//【注意!!!】别忘了这个break;
}
//当nums[i]小于所有排列好的数组元素时,将其插入位置0
else if (j == 0)
makRoom(0, i);
}
}
}
//将索引为num的元素插入到索引index处
void insertSort::makRoom(int index, int num)
{
int temp = nums[num];
for (int i = num; i >= index+1; i--)
{
nums[i] = nums[i - 1];
}
nums[index] = temp;
}
//打印排序后的数组
void insertSort::show()
{
for (int i = 0; i < nums.size(); i++)
cout << nums[i] << " ";
cout << endl;
}
四、堆排序【此处介绍大根堆】
大根堆概念:在树中除叶子节点外,所有根节点的值都大于其叶子节点。
完全二叉树相关概念:
- 不管是否为满完全二叉树,只要是完全二叉树,则其最后一个非叶子节点的在对应数组中的索引 = 树节点总量length/2。{此处根节点在数组中的索引为1,不是0}。
- 完全二叉树中父节点索引为i ----> 左子节点索引为2*i,右子节点索引为2*i+1;
完全二叉树中子节点索引为i ----> 父节点索引为i/2;
无序序列如图:
无序完全二叉树:
第一步,首先针对给出待排序数组,初始化构造一个大根堆。从最后一个非叶子节点开始,以倒序层序遍历的方式往根节点移动,保证移动过程中,经过的每个节点,以其为根节点的子树都满足大根堆。
(a)最后一个非叶子节点,满足以其为根节点的树为大根堆
(b)倒数第二个非叶子节点,以其为根节点的子树,不满足大根堆的要求,因此需要将其与左右节点中较大的节点值进行交换。
(c)倒数第三个非叶子节点,以其为根节点的子树,不满足大根堆的要求,因此需要将其与左右节点中较大的节点值进行交换。
(d)倒数第四个非叶子节点,以其为根节点的子树,不满足大根堆的要求,因此需要将其与左右节点中较大的节点值进行交换。但是,此时可以看到交换后的节点,其被交换的子节点处,不再满足大根堆的要求。
因此每次实际产生交换后,需要对其被交换的子节点进行大根堆判断,如果不是大根堆,则需要调整子节点与其下一代子节点的值,调整为大根堆。
(e)倒数第五个非叶子节点同上理。
经过上述五条,即可将原来的无序数组,调整为一个大根堆,此时其根节点的值为全局最大值。
/*
* 初始化整个数组元素构造成大根堆
* 前提知识:当一棵树有len个节点,根节点索引为1,则其最后一个非叶子节点在数组中的索引为len/2
*/
void maxHeapSort::maxHeap()
{
//获得当前树的最后一个非叶子节点
int k = length / 2;
//从最后一个非叶子节点索引开始层序遍历倒序往根节点移,保证以节点i为根节点的子树都满足大根堆
for (int i = length / 2; i >= 1; i--)
{
heapAdjust(i, length);
}
}
/*
* 从节点k开始,构造以节点k位根节点的子树为大根堆
*/
void maxHeapSort::heapAdjust(int k,int len)
{
int temp = 0;
for (int i = 2 * k; i <= len; i *= 2)
{
//找到节点k左右子节点中较大的那个
if (i < len && array[i] < array[i + 1])
{
i++;
}
//比较当前节点与其两个叶子节点最大的那个元素之间的大小关系
//如果当前节点>叶子节点的最大值,直接跳出循环
//如果当前节点<叶子节点的最大值,则交换当前节点与叶子节点值,
if (array[k] < array[i])
{
temp = array[k];
array[k] = array[i];
array[i] = temp;
//【注意】此时由于当前节点与叶子节点的值发生交换,可能会导致被交换的叶子树不再满足大根堆的要求
//因此需要将当前节点的索引移动到被交换的叶子索引处,继续向下检查每个节点的值是否都大于其两个叶子子节点
k = i;
}
else
break;
}
}
第二步,将获得的大根堆,根节点与其数组末尾元素进行交换,此时获得排序中的第一个数。由于此时大根堆中根节点不满足大根堆条件,则调整根节点与其子节点的值,验证被调整的子节点,向下深挖。即调用heapAdjust(1,len-1)。
第三步,将获得的大根堆,根节点与其数组末尾倒数第二个元素进行交换,此时获得排序中的第一个数。由于此时大根堆中根节点不满足大根堆条件,则调整根节点与其子节点的值,验证被调整的子节点,向下深挖。即调用heapAdjust(1,len-2)。
完整代码如下:
#include <iostream>
#include <vector>
using namespace std;
/*堆排序*/
class maxHeapSort {
private:
vector<int> array;
int length;
public:
maxHeapSort(vector<int> nums) :array(nums), length(nums.size()-1) {};
void heapAdjust(int k,int len);
void maxHeap();
void heapSort();
void show();
};
/*
* 从节点k开始,构造以节点k位根节点的子树为大根堆
*/
void maxHeapSort::heapAdjust(int k,int len)
{
int temp = 0;
for (int i = 2 * k; i <= len; i *= 2)
{
//找到节点k左右子节点中较大的那个
if (i < len && array[i] < array[i + 1])
{
i++;
}
//比较当前节点与其两个叶子节点最大的那个元素之间的大小关系
//如果当前节点>叶子节点的最大值,直接跳出循环
//如果当前节点<叶子节点的最大值,则交换当前节点与叶子节点值,
if (array[k] < array[i])
{
temp = array[k];
array[k] = array[i];
array[i] = temp;
//【注意】此时由于当前节点与叶子节点的值发生交换,可能会导致被交换的叶子树不再满足大根堆的要求
//因此需要将当前节点的索引移动到被交换的叶子索引处,继续向下检查每个节点的值是否都大于其两个叶子子节点
k = i;
}
else
break;
}
}
/*
* 初始化整个数组元素构造成大根堆
* 前提知识:当一棵树有len个节点,根节点索引为1,则其最后一个非叶子节点在数组中的索引为len/2
*/
void maxHeapSort::maxHeap()
{
//获得当前树的最后一个非叶子节点
int k = length / 2;
//从最后一个非叶子节点索引开始层序遍历倒序往根节点移,保证以节点i为根节点的子树都满足大根堆
for (int i = length / 2; i >= 1; i--)
{
heapAdjust(i, length);
}
}
void maxHeapSort::heapSort()
{
//将需要排列顺序的数组构造成一个大根堆
maxHeap();
//首先需要保证以nums[1]为根节点的树变为大根堆
for (int i = array.size() - 1; i >= 1; i--)
{
heapAdjust(1,i);
//对于数组长度为len的大根堆,将其num[1]与数组最后一个元素交换后,用于构造大根堆的数组缩短一个元素
//最大元素被保存在原数组的nums[i]中
int temp = array[1];
array[1] = array[i];
array[i] = temp;
}
}
//打印排序后的数组
void maxHeapSort::show()
{
for (int i = 0; i < array.size(); i++)
cout << array[i] << " ";
cout << endl;
}
五、归并排序
快速排序的原理:
采用递归方法,将整个数组进行不断划分,直到划分的每个字数组的长度为0或者为1,这时每个子数组都是有序数组,这是再按照有序数组的拼接算法,对每个子数组进行拼接,这样就能保证每次的拼接结果都还是有序的最终拼接成一个之后,整个数组便都是有序的,而数组的排序也宣布完成。
#include <iostream>
#include <vector>
using namespace std;
class mergeSort {
private:
vector<int> array;
int length;
public:
mergeSort(vector<int> nums) :array(nums), length(nums.size()) {};
~mergeSort() {};
vector<int> mergeArray(vector<int>& nums1, vector<int>& nums2);
void sort();
void show();
};
//将数组拆分成等分,递归,并在回溯期间进行有序数组的归并
vector<int> mergeSort::mergeArray(vector<int>& nums1, vector<int>& nums2)
{
//递归退出条件,当需要合并的两个数组中,其中一个数组为空,则返回另一个数组
if (nums1.size() == 0 || nums2.size() == 0)
return nums1.size() ? nums1 : nums2;
//将每个传进的来的数组都分割成两个数组继续递归
int mid1 = nums1.size() / 2;
vector<int> nums11 = vector<int>(nums1.begin(), nums1.begin() + mid1);
vector<int> nums12 = vector<int>(nums1.begin() + mid1, nums1.end());
int mid2 = nums2.size() / 2;
vector<int> nums21 = vector<int>(nums2.begin(), nums2.begin() + mid2);
vector<int> nums22 = vector<int>(nums2.begin() + mid2, nums2.end());
nums1 = mergeArray(nums11, nums12);
nums2 = mergeArray(nums21, nums22);
//在每层递归,将两个有序数组中的元素进行归并,合成一个数组后返回
vector<int> temp(nums1.size()+nums2.size(),0);
int index = 0;
int index1 = 0;
int index2 = 0;
while (index1 < nums1.size() || index2 < nums2.size())
{
if (index1<nums1.size()&&index2<nums2.size())
{
if(nums1[index1] > nums2[index2])
temp[index++] = nums2[index2++];
else
temp[index++] = nums1[index1++];
}
else if (index2 < nums2.size())
temp[index++] = nums2[index2++];
else if (index1 < nums1.size())
temp[index++] = nums1[index1++];
}
//返回两两有序数组的合成数组
return temp;
}
//将原数组拆分成两等份,进行递归,回溯时归并
void mergeSort::sort()
{
int mid = array.size() / 2;
vector<int> nums1 = vector<int>(array.begin(), array.begin() + mid);
vector<int> nums2 = vector<int>(array.begin() + mid, array.end());
array = mergeArray(nums1, nums2);
}
//打印排序后的数组
void mergeSort::show()
{
for (int i = 0; i < array.size(); i++)
cout << array[i] << " ";
cout << endl;
}
六、希尔排序
希尔排序原理:
- 首先将数组分为s = length/2份,对每一份进行插入排序;
- 再将数组分为s=s/2份,对每一份进行插入排序;
- ...,,直至将数组分成s = 1份,对该份进行插入排序;
代码中需要注意:每次不是将分出来的每份进行单独的插入排序,而是每次都是挨个元素遍历,在插入排序时,对当前所在组按照步长进行移动,具体可看代码如下:
#include <iostream>
#include <vector>
using namespace std;
/*希尔排序*/
class shellSort {
private:
vector<int> array;
int length;
public:
shellSort(vector<int> nums) :array(nums), length(nums.size()) {};
void makeRoom(int i, int j, int s);
void sort();
void show();
};
void shellSort::sort()
{
//将原始数组分成sp份
int sp = length / 2;
for (int s = sp; s >= 1; s /= 2)
{
cout <<"s: " << s << endl;
//首先要跳过每份的第1个元素
for (int i = s; i < length; i++)
{
//此处需要注意的是每次比较的步长为s
for (int j = i - s; j >= 0; j-=s)
{
if (array[i] >= array[j])
{
//将索引i的元素填充到索引j+s,即元素j所属份的下一步的索引处
makeRoom(j+s, i, s);
break;//【注意:别忘了这个break】
}
else if(j==0)
makeRoom(0, i ,s);
}
}
show();
}
}
//将索引为num的元素插入到索引index处
//记住,位移的步长为s
void shellSort::makeRoom(int index, int num, int s)
{
int temp = array[num];
if (num > index)
{
for (int i = num; i >= (index + s); i -= s)
{
array[i] = array[i - s];
}
array[index] = temp;
}
}
//打印排序后的数组
void shellSort::show()
{
for (int i = 0; i < array.size(); i++)
cout << array[i] << " ";
cout << endl;
}
七、简单选择排序
简单选择排序原理:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。
#include<iostream>
#include<vector>
using namespace std;
/*简单选择排序*/
class simpleSelectionSort {
private:
vector<int> nums;
public:
simpleSelectionSort(vector<int> temp) :nums(temp){};
void sort();
void show();
};
//每一次从未排序的部分中选取最小的元素,放到已排序部分最尾
//采用的是swap(已排序部分尾部+1,未排序部分中最小元素)的方式来放置
void simpleSelectionSort::sort()
{
for (int i = 0; i < nums.size(); i++)
{
int Min = nums[i];
int index = i;
for (int j = i; j < nums.size(); j++)
{
if (Min > nums[j])
{
Min = nums[j];
index = j;
}
}
//swap(已排序部分尾部 + 1,未排序部分中最小元素)
swap(nums[i], nums[index]);
}
}
//打印排序后的数组
void simpleSelectionSort::show()
{
for (int i = 0; i < nums.size(); i++)
cout << nums[i] << " ";
cout << endl;
}
八、基数排序
基数排序原理:将所有待比较数值(自然数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。
实现步骤:
- 确定数组中的最大元素有几位(MAX)(确定执行的轮数);
- 创建0~9个桶(桶的底层是队列),因为所有的数字元素都是由0~9的十个数字组成;
- 依次判断每个元素的个位,十位至MAX位,存入对应的桶中,出队,存入原数组;直 至MAX轮结束输出数组;
- 具体实现步骤如下图:
#include<iostream>
#include<vector>
#include<queue>
using namespace std;
/*基数排序*/
class radixSort
{
private:
struct ListNode
{
int val;
ListNode* next;
ListNode() :val(0), next(nullptr) {};
ListNode(int value) :val(value), next(nullptr) {};
ListNode(int value, ListNode* ptr) :val(value), next(ptr) {};
};
vector<ListNode*> array;
vector<int> nums;
public:
radixSort(vector<int> temp);
int getIndex(int num, int step);
void sort();
void show();
int maxStep();
//void sort(int* begin, int* end);
};
//构造函数,初始化桶数组为一个含有10个链表元素的数组
radixSort::radixSort(vector<int> temp)
{
array = vector<ListNode*>(10);
nums = temp;
}
//获得数字num第step位的数字大小,如果不存在,则直接返回0
int radixSort::getIndex(int num, int step)
{
int result = 0;
while (step != 0)
{
num = num / 10;
step--;
if (num == 0)
return 0;
}
result = num % 10;
return result;
}
//将待排序数组中的元素按照第step位依次放到桶中,并挨个桶取出返回新的数组
void radixSort::sort()
{
int step = 0;
//依次进行个位、十位、百位... ...的基数排序
while (step< maxStep())
{
vector<int> result;
auto start = nums.begin();
while (start != nums.end())
{
//将元素*start包装成一个链表节点
ListNode* node = new ListNode(*start);
//计算该元素在第step位的索引
int index = getIndex(*start, step);
//将包装好的节点查到数组对应索引的链表中,尾插法
if (array[index] == nullptr)
array[index] = node;
else
{
ListNode* temp = array[index];
while (temp != nullptr)
{
if (temp->next == nullptr)
{
temp->next = node;
break;
}
temp = temp->next;
}
}
start++;
}
//将数组中对应索引链表中的元素重新放入一个新的数组result中返回
for (int i = 0; i < array.size(); i++)
{
while (array[i] != nullptr)
{
result.push_back(array[i]->val);
array[i] = array[i]->next;
}
}
nums = result;
step++;
}
}
//获取待排序数组中最大元素的位数
int radixSort::maxStep()
{
int step = 0;
int Max = 0;
for (int i = 0; i < nums.size(); i++)
{
int temp = nums[i];
while (temp != 0)
{
temp = temp / 10;
step++;
}
Max = max(Max, step);
}
return Max;
}
//打印排序后的数组
void radixSort::show()
{
for (int i = 0; i < nums.size(); i++)
cout << nums[i] << " ";
cout << endl;
}
所有排序算法源码汇总:https://github.com/OYWALT/SortFunction