排序算法回顾(c++排序算法编写)

排序算法回顾

排序是程序设计里的重要操作也是基本必会的操作。正好在leetcode上刷到了排序题,这里就简单的将一些排序方法进行整理,也是自己的一个简单复习回顾。所有排序代码均采用c++编写。

例题:

给你一个整数数组 nums,请你将该数组升序排列。
示例 1:
输入:nums = [5,2,3,1]
输出:[1,2,3,5]
示例 2:
输入:nums = [5,1,1,2,0,0]
输出:[0,0,1,1,2,5] 
提示:
1 <= nums.length <= 50000
-50000 <= nums[i] <= 50000
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/sort-an-array

直接插入排序

直接插入排序是一种最简单的排序方法,它的基本操作是将一个记录插入到已经排好序的有序表中。

第i趟直接插入排序操作:
在有序子序列r[1…i-1]中插入r[i] 变成含有i个记录的有序序列r[1…i],和顺序查找类似。
整个排序过程是进行n-1趟插入,先将第1个记录作为一个有序的子序列,然后从第二个开始逐个插入。直至整个序列变为有序。
时间复杂度O(n^2)


class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        int n =  nums.size();
        int i,j,t;
        for (i = 1; i < n; i++) {
            t = nums[i];//待排序元素赋值给临时变量
            j = i;
            while (j > 0 && t < nums[j-1]) {//当未达到数组的第一个元素或者待插入元素小于当前元素
                nums[j] = nums[j-1];//就将该元素后移
                j--;
            }
            nums[j] = t;//插入位置找到
        }
        return nums;
    }
};


折半插入排序

单n的值较大时,我们需要对查找进行时间缩短,所以提出折半插入排序。


class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
         int left, right, mid;  
         int length = nums.size();
         int tmp;
         for (int i = 1; i < length; i++) {
        /* 找到数组中第一个无序的数,保存为tmp */
        if (nums[i] < nums[i - 1]) {
            tmp = nums[i];
        }
        else {
            continue;
        }
        /* 找到数组中第一个无序的数,保存为tmp */
        /* 二分查询开始 */
        left = 0;
        right = i - 1;
        while (left <= right) {
            mid = (left + right) / 2;
            if (nums[mid] > tmp) {
                right = mid - 1;
            }
            else {
                left = mid + 1;
            }
        }
        /* 二分查询结束,此时a[left]>=a[i],记录下left的值 */
        /* 将有序数组中比要插入的数大的数右移 */
        for (int j = i; j > left; j--) {
            nums[j] = nums[j - 1];
        }
        /* 将有序数组中比要插入的数大的数右移 */
        // 将left位置赋值为要插入的数
        nums[left] = tmp;
    }
    return nums;
    }
};


冒泡排序

冒泡排序思想是要把相邻的元素两两比较,当一个元素大 于右侧相邻元素时,交换它们的位置;当一个元素小于或等于右侧相 邻元素时,位置不变 。他是交换排序的一种,属于稳定排序。

class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        int n = nums.size();
        for (int i = 0; i < n-1; i++) {
            for(int j = 0;j < n-i-1; j++) {
                int t = 0;
                if(nums[j] > nums[j+1]){
                    t = nums[j];
                    nums[j] = nums[j+1];
                    nums[j+1] = t;
                }
            }
        }
        return nums;
    }
};


快速排序

同冒泡排序一样,快速排序也属于交换排序 ,通过元素之间的比较和 交换位置来达到排序的目的。
不同的是,冒泡排序在每一轮中只把1个元素冒泡到数列的一端,而快 速排序则在每一轮挑选一个基准元素,并让其他比它大的元素移动到 数列一边,比它小的元素移动到数列的另一边,从而把数列拆解成两个部分。
在分治法的思想下,原数列在每一轮都被拆分成两部分,每 一部分在下一轮又分别被拆分成两部分,直到不可再分为止。
每一轮的比较和交换,需要把数组全部元素都遍历一遍,时间复杂度是 O(n)。这样的遍历一共需要多少轮呢?假如元素个数是n,那么平均情 况下需要logn轮,因此快速排序算法总体的平均时间复杂度是O(nlogn) 。


class Solution {
    int partition(vector<int>& nums, int l, int r) {
        int pivot = nums[r];
        int i = l - 1;
        for (int j = l; j <= r - 1; ++j) {
            if (nums[j] <= pivot) {
                i = i + 1;
                swap(nums[i], nums[j]);
            }
        }
        swap(nums[i + 1], nums[r]);
        return i + 1;
    }
    int randomized_partition(vector<int>& nums, int l, int r) {
        int i = rand() % (r - l + 1) + l; // 随机选一个作为我们的主元
        swap(nums[r], nums[i]);
        return partition(nums, l, r);
    }
    void randomized_quicksort(vector<int>& nums, int l, int r) {
        if (l < r){
            int pos = randomized_partition(nums, l, r);
            randomized_quicksort(nums, l, pos - 1);
            randomized_quicksort(nums, pos + 1, r);
        }
    }
public:
    vector<int> sortArray(vector<int>& nums) {
        srand((unsigned)time(NULL));
        randomized_quicksort(nums, 0, (int)nums.size() - 1);
        return nums;
    }
};


堆排序

1.最大堆的堆顶是整个堆中的最大元素。
2. 最小堆的堆顶是整个堆中的最小元素。
以最大堆为例,如果删除一个最大堆的堆顶(并不是完全删除,而是跟 末尾的节点交换位置),经过自我调整,第2大的元素就会被交换上 来,成为最大堆的新堆顶。
堆排序算法的步骤:

  1. 把无序数组构建成二叉堆。需要从小到大排序,则构建成最大堆;需 要从大到小排序,则构建成最小堆。
  2. 循环删除堆顶元素,替换到二叉堆的末尾,调整堆产生新的堆顶。(漫画算法:小灰的算法之旅)

class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        int n = nums.size();
        //将数组变为堆
        heapify(nums);
        // 循环不变量:区间 [0, i] 堆有序
        for (int i = n - 1; i >= 1; ) {
            // 把堆顶元素(当前最大)交换到数组末尾
            swap(nums, 0, i);
            // 逐步减少堆有序的部分
            i--;
            // 下标 0 位置下沉操作,使得区间 [0, i] 堆有序
            siftDown(nums, 0, i);
        }
        return nums;
    }
    //数组变为堆
    void heapify(vector<int>& nums) {
        int n = nums.size();
        // 只需要从 i = (n - 1) / 2 这个位置开始逐层下移
        for (int i = (n - 1) / 2; i >= 0; i--) {
            siftDown(nums, i, n - 1);
        }
    }
    //param k    当前下沉元素的下标
    //end  [0, end] 是 nums 的有效部分
    //
    void siftDown(vector<int>& nums, int k, int end) {
        while (2 * k + 1 <= end) {
            int j = 2 * k + 1;
            if (j + 1 <= end && nums[j + 1] > nums[j]) {
                j++;
            }
            if (nums[j] > nums[k]) {
                swap(nums, j, k);
            } else {
                break;
            }
            k = j;
        }
    }
    void swap(vector<int>& nums, int index1, int index2) {
        int temp = nums[index1];
        nums[index1] = nums[index2];
        nums[index2] = temp;
    }
};


堆排序快速排序异同

堆排序和快速排序的平均时间复杂度都是O(nlogn) ,并且都是不稳定排序 。至于不同点,快速排序的最坏时间复杂度是O(n^2)) ,而堆排序的最坏时间复 杂度稳定在O(nlogn) 。此外,快速排序递归和非递归方法的平均空间复杂度都是O(logn) ,而堆排序的空间复杂度是O(1) 。

归并排序

划分问题:把序列分成元素个数尽量想等的两半。
递归求解:把两半元素分别排序。
合并问题:把两个有序表合并成一个。
关键是两表合并。可以每次只需要把两个序列的最小元素相比较,删除其中的较小元素并加入合并后的新表。

递归调用函数 mergeSort(nums, l, mid) 对 nums 数组里 [l,mid] 部分进行排序。
递归调用函数 mergeSort(nums, mid + 1, r) 对 nums 数组里 [mid+1,r] 部分进行排序。
此时 nums 数组里[l,mid]和[mid+1,r] 两个区间已经有序我们对两个有序区间线性归并即可使nums数组里[l,r] 的部分有序。
线性归并的过程并不难理解,由于两个区间均有序,所以我们维护两个指针 ii 和 jj 表示当前考虑到 [l,mid]里的第i个位置和 [mid+1,r] 的第 j个位置。
如果 nums[i] < nums[j] ,那么我们就将nums[i] 放入临时数组 tmp 中并让 i += 1 ,即指针往后移。否则我们就将 nums[j] 放入临时数组 tmp 中并让 j += 1 。如果有一个指针已经移到了区间的末尾,那么就把另一个区间里的数按顺序加入 tmp 数组中即可。

这样能保证我们每次都是让两个区间中较小的数加入临时数组里,那么整个归并过程结束后[l,r] 即为有序的。

class Solution {
    vector<int> tmp;
    void mergeSort(vector<int>& nums, int l, int r) {
        if (l >= r) return;
        int mid = (l + r) >> 1;
        mergeSort(nums, l, mid);
        mergeSort(nums, mid + 1, r);
        int i = l, j = mid + 1;
        int cnt = 0;
        while (i <= mid && j <= r) {
            if (nums[i] < nums[j]) {
                tmp[cnt++] = nums[i++];
            }
            else {
                tmp[cnt++] = nums[j++];
            }
        }
        while (i <= mid) tmp[cnt++] = nums[i++];
        while (j <= r) tmp[cnt++] = nums[j++];
        for (int i = 0; i < r - l + 1; ++i) nums[i + l] = tmp[i];
    }
public:
    vector<int> sortArray(vector<int>& nums) {
        tmp.resize((int)nums.size(), 0);
        mergeSort(nums, 0, (int)nums.size() - 1);
        return nums;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值