常用排序算法(C++)



冒泡排序(稳定排序)

(1)算法思路

​ 冒泡排序的思想就是比较当前数和后一个数的大小,将较大的数往后移动,这样可以确保一轮下来能将最大的数放在数组的最末端。然后重复此操作即可完成排序。

(2)算法步骤

在这里插入图片描述

上面第一轮比较完,我们可以看到最大的数5已经被放在了最端,此时我们只需要将去掉最大的数的那部分(2,3,1,4)进行重复的操作。

在这里插入图片描述

(3) 算法描述

void bubbleSort(vector<int>& arr) {
    int n = arr.size();
    for (int i = 0; i < n - 1; ++i) {
        for (int j = 0; j < n - i - 1; ++j) {
            if (arr[j] > arr[j+1]) {
                int temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
            }
        }
    }
}

(4)时间复杂度

冒泡排序最好的时间复杂度为 O(n),冒泡排序的最坏时间复杂度为O(n2)

综上,因此冒泡排序总的平均时间复杂度为O(n2)

归并排序(稳定排序)

(1)算法思路

​ 待排序的元素分解成两个规模大致相等的子序列。如果不易分解,将得到的子序列继续分解,直到子序列中包含的元素个数为1。因为单个元素的序列本身就是有序的,此时便可以进行合并,从而得到一个完整的有序序列。即先使每个子序列有序,再使子序列段间有序。

(2)算法步骤

(1)分解:
    将待排序的元素分成大小大致一样的两个子序列。
(2)治理:
    对两个子序列进行个并排序。
(3)合并:
    将排好序的有序子序列进行合并,得到最终的有序序列。

在这里插入图片描述

(3) 算法描述

mergeSort(nums, 0, nums.size()-1);
//归并排序
void mergeSort(vector<int>& nums, int left, int right) {	
    if(left < right) {
        int mid = (left + right) / 2;
        mergeSort(nums, left, mid);			//对 nums[left,mid]进行排序
        mergeSort(nums, mid+1, right);		//对 nums[mid+1,right]进行排序
        merge(nums, left, mid, right);		//进行合并操作
    }
}
//合并函数
void merge(vector<int>& nums, int left, int mid, int right) {
    vector<int> temp(right-left+1);
    int i = left, j = mid+1, k = 0;
    while(i <= mid && j <= right) {
        if(nums[i] <= nums[j]) {
            temp[k++] = nums[i++];		//按从小到大存放在 temp 数组里面
        } else {
            temp[k++] = nums[j++];
        }
    }
    while(i <= mid) {			// j 序列结束,将剩余的 i 序列补充在 temp 数组中 
        temp[k++] = nums[i++];
    }
    while(j <= right) {			// i 序列结束,将剩余的 j 序列补充在 temp 数组中 
        temp[k++] = nums[j++];
    }
    for(int i = left, k = 0; i <= right; ++i) {
        nums[i] = temp[k++];
    }
}

(4)时间复杂度

从上文的图中可看出,每次合并操作的平均时间复杂度为O(n),而完全二叉树的深度为|log2n|。总的平均时间复杂度为O(nlogn)。而且,归并排序的最好,最坏,平均时间复杂度均为O(nlogn)

快速排序(不稳定排序)

(1) 算法思路

​ 它的基本思想是将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据小,然后再按此方法对这两部分数据进行快速排序,整个排序过程可以递归进行,以此使所有数据变成有序序列。

	先从数列中取出一个元素作为基准元素。依基准元素为标准,将问题分解为两个子序列,使小于等于基准元素的子序列在左侧,使大于基准元素的子序列在右侧。

(2) 算法步骤

​ 假设当前的待排序的序列为 R[low, high] , 其中 low <= high。同时选取首元素为基准元素。

  • 步骤一:选取首元素的第一个元素作为基准元素 pivot = R[low] ,i = low ,j = high;

  • 步骤二:用 j 从右向左扫描,找第一个小于等于 pivot 的数,如果找到,R[i] 和 R[j] 交换 ,i++;

  • 步骤三:用 i 从左向右扫描,找第一个大于 pivot 的数,如果找到,R[i] 和 R[j] 交换,j–;

  • 步骤四:重复步骤二 ~ 步骤三,直到 j 与 i 的指针重合,返回基准元素位置 mid = i 。

​ 至此为一趟排序,此时以 mid 为界线,将数据分割为两个子序列,左侧子序列都比 pivot 数小,右侧子序列都比 pivot 数大,然后再进行递归,分别对这两个子序列进行快速排序。

(3) 举例图解

​ 以序列(30,24,5,58,18,36,12,42,39)为例,进行图解。

  • 初始化,i = low ,j = high,pivot = R[low] = 30。如下图所示:

  • 从右向左扫描,从数组的右边位置向左找,一直找一个小于等于 pivot 的数,找到R[j] = 12,R[i]与R[j]交换,i++。如下图所示:


在这里插入图片描述

  • 从左向右扫描,从数组的左边位置向右找,一直找到一个比 pivot 大的数,找到 R[i] = 58,R[i] 与 R[j] 交换 ,j–。如下图所示:

在这里插入图片描述

在这里插入图片描述

  • 从右向左扫描,从数组的右边位置向左找,一直找到小于等于 pivot 的数,找到R[j] = 18,R[i]与R[j]交换,i++。如下图所示:

在这里插入图片描述

在这里插入图片描述

  • 直到 j 与 i 的指针重合,则找到了基准元素的位置 i ,保证左侧子序列都小于等于基准元素,右侧子序列都大于基准元素。说明基准元素 pivot = 30 找到了其正确排序后的位置。

  • 然后再分别对这两个序列(12,24,5,18)和(36,58,42,39)进行快速排序(递归)。

(4) 算法描述

// 对数组nums在区间[low : high]内进行快速排序
void quickSort(vector<int>& nums, int low, int high) {
    if(low < high) {
        int mid = part(nums, low, high);	//返回基准元素位置
        quickSort(nums, low, mid-1);		//左区间递归快速排序
        quickSort(nums, mid+1, high);		//右区间递归快速排序
    }
}

// 划分函数:确定基准元素的位置,使其左边序列都小于等于基准元素,右边序列都大于基准元素
int part(vector<int>& nums, int low, int high) {
    int midNum = nums[low];		//基准元素
    while(low < high) {
        // 从右向左开始找一个小于等于midNum的数值
        while(low < high && nums[high] > midNum) {
            --high;
        }
        // 找到后交换nums[low]和nums[high],同时low向右移动一位
        if(low < high) {
            swap(nums[low++], nums[high]);
        }
        // 从左向右开始找一个大于midNum的数值
        while(low < high && nums[low] <= midNum) {
            ++low;
        }
        // 找到后交换nums[low]和nums[high],同时high向左移动一位
        if(low < high) {
            swap(nums[low], nums[high--]);
        }
    }
    return low;
}

堆排序(不稳定排序)

(1) 算法思路

堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏最好,平均时间复杂度均为O(nlogn),是不稳定排序。

堆是具有以下性质的完全二叉树,每个节点的值都大于或等于其左右孩子节点的值,称为大顶堆。每个结点的值都小于或等于其左右孩子节点的值,称为小顶堆。注意:没有要求节点的左孩子的值和右孩子的值的大小关系。

大顶堆举例:

在这里插入图片描述

对堆中的节点按层进行编号,映射到数组中就是下面这个样子:

在这里插入图片描述

(2) 算法步骤

  • 将待排序序列构造成一个大顶堆,构造步骤如下:
    • 从最后一个非叶子节点开始,对应数组下标n/2 - 1,找其子节点,看是否比它大,若比它大的话,就交换节点,交换之后继续向下检查;
    • 再继续找上面的非叶子节点,进行相同操作;
    • 直到根节点交换完成后,此时根节点就是最大的。
  • 此时整个序列的最大值就是顶堆的根节点
  • 将其与末尾元素进行交换,此时末尾就为最大值
  • 然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。

可以看到在构建大顶堆的过程中,元素的个数逐渐减少,最后就得到一个有序序列了

(3) 代码实现

void adjustHeap(vector<int>& v, int n, int i) {
    int largest = i;
    int left = 2 * i + 1, right = 2 * i + 2;
    if(left < n && v[left] > v[largest]) {
        largest = left;
    }
    if(right < n && v[right] > v[largest]) {
        largest = right;
    }
    // 如果最大值不是根节点
    if(largest != i) {
        swap(v[i], v[largest]);
        adjustHeap(v, n, largest);     // 递归地堆化子树
    }
}

void heapSort(std::vector<int>& arr) {
    int n = arr.size();
    // 构建堆(重组数组)
    for(int i = n/2 - 1; i >= 0; --i) {
        adjustHeap(arr, n, i);
    }
    for(int i = n-1; i > 0; --i) {
        swap(arr[0], arr[i]);       // 将当前根节点移动到数组的末尾
        adjustHeap(arr, i, 0);      // 调整剩余元素以保持堆性质
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值