排 序 总 结

本文详细介绍了排序算法,包括冒泡排序、简单选择排序、直接插入排序、希尔排序、快速排序、堆排序和归并排序。每种排序算法的时间复杂度、空间复杂度以及稳定性进行了说明,并提供了C++实现的代码示例。这些排序算法是计算机科学中的基础知识,对于理解数据处理和优化算法至关重要。

排序相关的概念

排序的稳定性

假设Ki = Kj ,且在排序前的序列中ri 领先于rj (即i < j)。
如果排序后ri 仍领先于rj,则称所用的排序方法是稳定的。
反之,若排序后ri 落后于rj,则称所用的排序方法是不稳定的。

如排序前的序列为:2, 3(A), 6, 4, 5, 3(B), 9

若排序后:2, 3(A), 3(B), 4, 5, 6, 9
则该排序方法是稳定的。

若排序后:2, 3(B), 3(A), 4, 5, 6, 9
则该排序方法是不稳定的。

冒泡排序(Bubble Sort)

解析:

冒泡排序是一种交换排序,它的基本思想是:两两比较相邻记录的关键字,如果反序则交换,直到没有反序的记录为止。这个过程类似水中气泡往上浮,故而叫冒泡法。
时间复杂度:O(N2)
空间复杂度:O(1)

代码实现:

// 升序
void swap(int& a, int& b)
{
    int t = a;
    a = b;
    b = t;
}
void BubbleSort(vector<int> &nums)
{
	//注意这里赋值是为了nums为空的情况,nums.size()是unsigned,nums.size() - 1,会得到一个很大的数字。
    int vecSize = nums.size();
    // i 表示排好序的数字数量,所以只需要排好(vecSize-1)个数字就可以了
    // j 表示剩下未排好序的数字需要比较多少次才可以找出一个最大值
    for (int i = 0; i < vecSize - 1; i++)
    {
        for (int j = 0; j < vecSize - i - 1; j++)
        {
            if (nums[j] > nums[j+1])
				swap(nums[j], nums[j + 1]);
        }
    }
}

简单选择排序(Simple Selection Sort)

解析:

简单选择排序法就是通过(n-i)次关键字间的比较,从(n-i+1)个记录中找出关键字最小的记录,并和第i(1<=i<=n)个记录交换,直到最后一个记录为止。
相比冒泡法来说,其数据移动次数大大减小,提高了一定的效率。
时间复杂度:O(n2)
空间复杂度:O(1)

代码实现:

void SelectSort(vector<int> &nums)
{
	int vecSize = nums.size();
    for (int i = 0; i < vecSize - 1; i++)
    {
        int minIdx = i;
        for (int j = i + 1; j < vecSize; j++)
        {
            if (nums[j] < nums[minIdx])
                minIdx = j;
        }
        if (i != minIdx)
            swap(nums[i], nums[minIdx]);
    }
}

直接插入排序(Straight Insertion Sort)

解析:

直接插入排序的基本操作是将一个记录插入到已经排好序的有序表中,从而得到一个新的、记录数增1的有序表。
时间复杂度:O(n2)
空间复杂度:O(1)
*虽然时间复杂度相同,但是直接插入排序法比冒泡法和简单选择排序法的性能要好一些。

代码实现:

void InsertSort(vector<int> &nums)
{
    for (int i = 1; i < nums.size(); i++)
    {
        if (nums[i] < nums[i - 1])
        {
            int x = nums[i];
            int j = i;
            for (; j > 0 && x < nums[j - 1]; j--)
                nums[j] = nums[j - 1];
            nums[j] = x;
        }
    }
}

希尔排序(Shell Sort)

解析:

希尔排序实际上是一种性能优化的插入排序,基本思想是:先将整个待排序记录序列按下标的一定增量分组,对每组使用直接插入排序算法排序,待整个序列中的序列基本有序之后,在对全体记录进行依次直接插入排序。
希尔排序不是一种稳定的排序方法。
所谓基本有序,是指小的关键字基本在前面,大的基本在后面,不大不小的基本在中间,像{2, 1, 3, 6, 4, 7, 5, 8, 9}就可以称为基本有序,而像{1, 5, 7, 3, 7, 8, 2, 4, 6}就谈不上基本有序了。
时间复杂度:O(n3/2)
空间复杂度:O(1)

代码实现:

void ShellSort(vector<int> &nums)
{
    for (int step = nums.size() >> 1; step > 0; step >>= 1)
    {
        for (int i = step; i < nums.size(); i++)
        {
            if (nums[i] < nums[i - step])
            {
                int x = nums[i];
                int j = i;
                for (; j >= step && x < nums[j - step]; j -= step)
                    nums[j] = nums[j - step];
                nums[j] = x;
            }
        }
    }
}

快速排序(Quick Sort)

解析:

基本思想是:通过一趟排序将待排序序列分割为独立的两个序列,其中一个序列中的数值比另一个序列的都大,然后分别对这两个序列进行排序,直到整个序列都是有序为止。

  1. 时间复杂度:
    最优和平均:O(nlogn)
    最差:O(n2) – 数组是正序或者倒序,和所有元素都相同。
  2. 空间复杂度:
    最优和平均:O(logn)
    最差:O(n)

代码实现:

class Solution {
public:
    int Partition(vector<int>& nums, int l, int r)
    {
        int idx = l + rand()%(r - l + 1);// 取[l,r]间的随机数
        int pivot = nums[idx];
        swap(nums[l], nums[idx]);// 交换双方是nums元素,不是pivot !

        while (l < r)
        {
            while (l < r && nums[r] >= pivot) // 注意这里>=,也就是等于的话不用动!!
                r--;
            nums[l] = nums[r];

            while (l < r && nums[l] <= pivot)// 注意这里>=,也就是等于的话不用动!!
                l++;
            nums[r] = nums[l];
        }
        nums[l] = pivot;
        return l;
    }
    void quickSort(vector<int>& nums, int l, int r)
    {
        if (l < r)
        {
            int i = Partition(nums, l, r);
            quickSort(nums, l, i - 1);
            quickSort(nums, i + 1, r);
        }
    }
    vector<int> sortArray(vector<int>& nums) {
        srand(time(0));//随机数播种
        quickSort(nums, 0, nums.size() - 1);
        return nums;
    }
};

堆排序(Heap Sort)

解析:

堆是具有下列性质的完全二叉树:每个节点的值都大于或者等于其左右子节点的值,称为大顶堆;或者每个节点的值都小于或者等于其左右子节点的值,称为小顶堆。
堆排序就是利用堆进行排序的方法,基本思想是,将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与堆数组末尾的元素交换,此时末尾就是最大值了,然后将剩余的n-1个元素重新构造成一个堆,这样就会得到n个元素中的次小值。如此反复执行,便能得到一个有序序列了。
堆排序是一种不稳定的排序方法。
堆中节点索引关系:
当前节点的索引为i,则:
其父节点为 (i - 1) / 2
其左子节点为 (2i + 1)
其右子节点为 (2
i +2)
时间复杂度:O(nlogn)
空间复杂度:O(1)

代码实现:

class Solution {
public:
    void adjustHeap(vector<int>& nums, int parIndex, const int& len)
    {
        int parVal = nums[parIndex];
        int childIdx = (parIndex << 1) + 1; // left

        while (childIdx < len)
        {
            if (childIdx + 1 < len && nums[childIdx + 1] > nums[childIdx])
                childIdx++; // right is bigger, so right ...
            
            if (parVal >= nums[childIdx]) // parent is biggest, break ...
                break;
            
            nums[parIndex] = nums[childIdx];
            parIndex = childIdx;
            childIdx = (parIndex << 1) + 1;
        }
        nums[parIndex] = parVal;
    }
    vector<int> sortArray(vector<int>& nums) {
        int n = nums.size();

        // 将数组看成是完全二叉树的层序遍历,建大顶堆,堆顶的数最大
        for (int i = ((n - 1) >> 1); i >= 0; i--)
            adjustHeap(nums, i, n);

        // 交换堆顶元素和未排序最后一个元素
        for (int i = n - 1; i > 0; i--)
        {
            swap(nums[0], nums[i]);
            adjustHeap(nums, 0, i);
        }

        return nums;
    }
};

归并排序(Merge Sort)

解析:

归并排序中的归并在数据结构中的定义是将两个或者两个以上的有序表组合成一个新的有序表。
归并排序的原理是假设初始序列含有n个记录,则可以看成n个有序的子序列,每个子序列的长度为1,然后两两归并,得到(n/2)个长度为2或者1的有序子序列;在两两归并,…,如此重复,直到得到一个长度为n的有序序列为止。
归并排序是一种稳定的排序方法。
时间复杂度:O(nlogn) – 最好,平均,最坏
空间复杂度:O(n)

代码实现:

递归:

class Solution {
public:
    void mergeArray(vector<int>& nums, vector<int>& temp, int left, int mid, int right)
    {
        int l = left;
        int r = mid + 1;
        int i = 0;

        // 取二者中小的放到辅助数组中
        while (l <= mid && r <= right)
        {
            if (nums[l] < nums[r])
                temp[i++] = nums[l++];
            else
                temp[i++] = nums[r++];
        }

        while (l <= mid)
            temp[i++] = nums[l++];

        while (r <= right)
            temp[i++] = nums[r++];
        
        for (int j = left; j <= right; j++)
            nums[j] = temp[j - left];
    }
    void mergeSort(vector<int>& nums, vector<int>& temp, int l, int r)
    {
        if (l < r)
        {
            int m = l + ((r - l) >> 1);
            mergeSort(nums, temp, l, m);
            mergeSort(nums, temp, m + 1, r);
            mergeArray(nums, temp, l, m, r);
        }
    }
    vector<int> sortArray(vector<int>& nums) {
        vector<int> temp(nums.size());
        mergeSort(nums, temp, 0, nums.size() - 1);
        return nums;
    }
};

迭代:

void MergeArray(vector<int> &nums, int low, int mid, int high)
{
    vector<int> temp(high - low + 1, 0);
    int lowIdx = low;
    int highIdx = mid + 1;
    int tempIdx = 0;

    // 比较两个数组中的元素,并根据大小合并入临时数组中
    while (lowIdx <= mid && highIdx <= high)
    {
        if (nums[lowIdx] < nums[highIdx])
            temp[tempIdx++] = nums[lowIdx++];
        else
            temp[tempIdx++] = nums[highIdx++];
    }

    // 将两个数组中剩余的部分放到临时数组中
    while (lowIdx <= mid)
        temp[tempIdx++] = nums[lowIdx++];
    
    while (highIdx <= high)
        temp[tempIdx++] = nums[highIdx++];
    
    // 将临时数组中的排好序的元素放入原数组中
    for (int i = low; i <= high; i++)
    {
        nums[i] = temp[i - low];
    }
    //PrintVector(nums);
}
void MergeSortLoop(vector<int> &nums)
{
    int numsSize = nums.size();
    int step = 1;

    while (step < numsSize)
    {
        cout << "step: " << step << endl;
        for (int low = 0; low < numsSize - step + 1; low += step*2)
        {
            int mid = low + step - 1;
            int high = low + step * 2 - 1;
            high = (high > numsSize-1)?numsSize-1:high;
            MergeArray(nums, low, mid, high);
        }

        //PrintVector(nums);
        step <<= 1;
    }
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值