冒泡排序(Bubble Sort)
实现思路:
逐步比较相邻元素,如果满足条件(大于或小于)则交换相邻元素,每一次遍历都将最大(最小)元素排序到最后,时间复杂度为O(n^2)。
示例:
升序排列
1、从第一个元素开始,之间比较相邻元素,如果前一个元素大于后一个元素则交换位置。向后遍历直到遍历到最后一个元素,此时,最后一个元素已排好序(最大值)。
2、重复步骤1,只对未排好序的元素进行遍历排序。
vector<int>nums = {15,62,34,15,721,64,3298,4,62,6};
for(int i = nums.size() - 1; i >= 0; i--)
{
for(int j = 0; j < i; j++){
if(nums[j]>nums[j+1]){
swap(nums[j],nums[j+1]);
}
}
}
选择排序(Selection Sort)
实现思路:
每一次扫描一遍未排序区域,从未排序区域中找出最小(最大)值,然后将这个值放到已排序序列的开头。时间复杂度O(n^2)。
示例:
升序排列:
for(int i = 0; i < nums.size(); i++)
{
int mindex = i;
for(int j = i+1; j < nums.size(); j++){//从未排序区域里面找
if(nums[j]<nums[i]){//未排序区域里面找到比选择值更小的元素,记录下来位置
mindex = j;
}
}
swap(nums[i],nums[mindex]);//将最值移动到开头
}
插入排序(Insertion Sort)
实现思路:
将序列分为已排序和未排序两部分,每次从未排序序列中选择一个元素插入到已排序的部分中,直到所有的元素都被插入到已排序部分。时间复杂度O(n^2)。
示例:
for (int i =1; i < nums.size(); i++)//默认numsp[0]是排好序的
{
int j = i;
int temp = nums[j];//记录要插入的初始值,因为nums[i]有可能会被nums[i-1]覆盖
while (j > 0 && nums[j - 1] > temp) {//寻找合适的插入位置,如果前面的值大于当前值,则把前面的值往后移动
nums[j] = nums[j - 1];
j--;
}
nums[j] = temp;
}
希尔排序(Shell Sort)
实现思路:
希尔排序是插入排序的一种改进版本,思路是将原序列按照分量(开始分量一般置为len/2)分为若干子序列,然后对每个子序列进行排序,然后缩小增量,减小子序列的大小,继续排序。直到增量为1时完成排序。时间复杂度为O(n^1.3)。
示例:
vector<int>nums = {15,62,34,14,4,61};//len = 6, 开始增量gap = 6 /2 = 3,然后进行分组。nums[3],nums[0]一组(nums[i],nums[i-gap],nums[i-gap-gap]一组)
//第一次分组后:nums[3],nums[0]一组,nums[4],nums[1]一组,nums[5],nums[2]一组,将对应的组排序后变成 14,4,34,15,62,61
//第二次分组后:gap = 1,变为对全排列进行插入排序
算法示例:
int len = nums.size();
int gap = len /2; //按照gap这个分量进行分组
while(gap > 0){ //分组的长度大于1时都将继续分组
for(int i = gap; i < nums.size(); i++){
int j = i;
int temp = nums[j];
while(j > gap && nums[j - gap] > nums[j]){
nums[j] = nums[j-gap];
j -= gap;
}
gap /= 2;
}
}
归并排序(Merge Sort)
实现思路:
归并排序是基于分治的思路,首先将序列分为两个子序列,对每个子序列进行递归排序,然后合并两个有序子序列,直到合并整个序列,时间复杂度为O(nlogn)。
示例:
void MergeSort(vector<int>&nums,int left, int right){
if(left<right){
int mid = (left+right)/2;
MergeSort(nums,left,mid);
MergeSort(nums,mid+1,right);
Merge(nums,left,right,mid);
}
}
void Merge(vector<int>&nums,int left, int right,int mid){
int i = left;
int j = mid+1;
int index = left;
vector<int>temp(nums.size(),0);
while(i <= mid && j <= right){
if(nums[i]<nums[j]){
temp[index++] = nums[i++];
}else{
temp[index++] = nums[j++];
}
}
while(i<=mid){
temp[index++] = nums[i++];
}
while(j<=right){
temp[index++] = nums[j++];
}
for(int k = left; k <= right; k++){
nums[k] = temp[k];
}
}
快速排序(Quickly Sort)
实现思路:
快速排序是基于分治的思想(分组),首先选取一个基准值,将小于基准值的数字放左边,大于基准值的数字放右边。然后对左右两边的数组进行递归处理。时间复杂度O(nlogn)。
示例:
void QuickSort(vector<int>&nums, int left, int right) {
if (left < right) {
int i = left;
int j = right;
int temp = nums[i];
while (i < j) {
while (i < j && nums[j] > temp)j--;
nums[i++] = nums[j];//第一次覆盖基准值的位置,之后覆盖重复值的位置
while (i < j && nums[i] < temp)i++;
nums[j--] = nums[i];//重复覆盖重复值的位置
}
nums[i] = temp;
QuickSort(nums, left, i - 1);
QuickSort(nums, i + 1, right);
}
}
堆排序(Heap Sort)
实现思路:
堆排序是一种基于比较的算法,堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子节点的键值或索引总是小于(或者大于)它的父节点。
在堆排序算法中,我们通常使用大根堆(最大堆)来进行升序排序,或者使用小根堆(最小堆)来进行降序排序。这里以升序排序和大根堆为例来说明堆排序的过程。
堆排序可以分为两个主要阶段:
- 构建初始堆:将给定无序数组构造成一个大根堆,此时,整个数组的最大值就是堆顶的根节点。这个过程通常从最后一个非叶子节点开始,向前遍历到根节点,对每个遍历到的节点进行堆化(Heapify)操作,以确保每个节点都满足大根堆的性质。
- 排序:在初始堆构建完成后,堆顶元素(即数组的第一个元素)就是当前的最大值。我们将其与数组的最后一个元素交换,然后将堆的大小减一(因为最后一个元素现在已经被视为“排序完成”并移出了堆的考虑范围)。之后,对新的堆顶元素(即原数组的倒数第二个元素,但现在在数组中的位置是第一个)进行堆化操作,以确保剩余的元素仍然构成一个大根堆。这个过程重复进行,每次都将当前的最大值“移出”堆(实际上是放到了数组的末尾),并重新调整堆,直到堆的大小为1,此时数组就完全有序了。
堆排序的时间复杂度是O(n log n),同时,堆排序是一种不稳定的排序算法,因为相同的元素可能在排序过程中因为交换而改变它们之间的相对顺序。
示例:
void Heapify(int n, int i, vector<int>&nums) {
int largest = i;
int leftIndex = 2 * i + 1;
int rightIndex = 2 * i + 2;
if (leftIndex < n && nums[leftIndex] > nums[largest]) {
largest = leftIndex;
}
if (rightIndex < n && nums[rightIndex] > nums[largest]) {
largest = rightIndex;
}
if (i != largest) {
swap(nums[i], nums[largest]);
Heapify(n, largest, nums);
}
}
void HearSort(vector<int>&nums) {
for (int i = nums.size()/2 - 1; i >= 0; i--) {
Heapify(nums.size(), i, nums);
}
for (int i = nums.size() - 1; i >= 0; i--) {
swap(nums[0], nums[i]);
Heapify(i, 0, nums);
}
}
为什么第一次堆排序从最后一个非叶子节点开始往上堆化,而之后从根节点开始堆化?
从最后一个非叶子节点开始向上进行堆化原因是叶子节点自然满足堆的性质,而非叶子节点可能不满足堆的性质。
而之后从根节点开始堆化的原因是,除了根节点外,其余节点都满足堆的性质,且在交换之前,根节点下面的两个节点是除根节点之外的最大节点,因此在交换之后,只需要对根节点进行堆化,将其调整满足堆的性质即可。
计数排序
实现思路:
计数排序是非比较型整数排序,思路在于将数值出现的次数存储在额外开辟的数组空间中,然后遍历次数数组,按照次数依次将数字输出到原数组上。
示例:
void CountingSort(vector<int>&nums) {
if (nums.empty()) return;
int maxVal = INT_MIN;
int minVal = INT_MAX;
for (int i = 0; i < nums.size(); i++) {
if (maxVal < nums[i]) {
maxVal = nums[i];
}
if (minVal > nums[i]) {
minVal = nums[i];
}
}
int range = maxVal - minVal + 1;
vector<int>temp(range, 0);
for (int i = 0; i < nums.size(); i++) {
temp[nums[i] - minVal]++;
}
int index = 0;
for (int i = 0; i < temp.size(); i++) {
while (temp[i] > 0)
{
nums[index++] = i + minVal;
temp[i]--;
}
}
}
桶排序(Bucket Sort)
实现思路:
桶排序是一种一种分布式排序算法,它将数组分到有限数量的桶里,每个桶再单独排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序)。桶排序是稳定的排序算法,其时间复杂度依赖于数据的分布。
示例:
void BucketSort(vector<int>&nums) {
int minVal = *min_element(nums.begin(), nums.end());
int maxVal = *max_element(nums.begin(), nums.end());
int bucketNum = max(1, (maxVal - minVal) / 5 );
vector<vector<int>>buckets(bucketNum + 1);
for (auto num : nums) {
int index = (num - minVal) * bucketNum / (maxVal - minVal);
buckets[index].push_back(num);
}
int start = 0;
for (int i = 0; i <= bucketNum; i++) {
QuickSort(buckets[i],0,buckets[i].size()-1);//调用上面的快速排序
for (int j = 0; j < buckets[i].size(); j++) {
nums[start++] = buckets[i][j];
}
}
}
基数排序(Radix Sort)
实现思路:
基数排序的思路是,按照给定序列中的最大元素的位数(如最大元素为123,则位数为3)从最低位开始循环排序,(将数列先按个位数排序,再按十位数,再按排位数),经过从最低位的循环后,将桶中的元素去除,这时排序后的序列个位是有序的。经过十位数的循环排序后,取出的数列个位和十位都是有序的,依次循环排序得到有效排序数组。
示例:
注:此示例只适用于大于0的整数排序。
int GetNumMaxDigit(vector<int>&nums) {
int maxVal = *max_element(nums.begin(), nums.end());
int digit = 0;
while (maxVal > 0)
{
maxVal /= 10;
digit++;
}
return digit;
}
void RadixSort(vector<int>& nums) {
int maxDigit = GetNumMaxDigit(nums);
for (int digit = 1; digit <= maxDigit; digit++) {//从最低位到最高位都遍历一遍
vector<vector<int>>buckets(10, vector<int>());//放排序位为0-9的数字
for (auto num : nums)
{
int curDigitNum = (int)(num / pow(10, digit - 1)) % 10;
buckets[curDigitNum].push_back(num);
}
int index = 0;
for (int i = 0; i < buckets.size(); i++) {//将排好序后的数组复制回原序列
for (int j = 0; j < buckets[i].size(); j++) {
nums[index] = buckets[i][j];
index++;
}
}
}
}