排序算法总结(C++)(第一部分)

排序算法总结(C++)(第一部分)

在这篇文章中,记录了自己学习的经典排序算法,代码语言用的C++。现阶段,排序算法已经非常成熟,有各种封装的库函数。但是还是要学习并自己写出代码,一方面理解排序算法可以帮助我们锻炼思维能力,另一方面自己给出代码可以增加我们的码农能力。而在这里记录总结下来,一方面和大家分享探讨,另一方面有不对的地方,希望大家给我指导改正。此外,也能锻炼自己的学习总结能力。

本博客基本内容分为两部分:

  • 第一部分:基于比较的排序算法
  • 第二部分:不基于比较的排序算法

1.冒泡排序

这是最简单直接的方法。

  • 从数组左边到右边,依次两两比较,将较大的交换到后面,直到数组末尾位置。
  • 在从剩余未排序元素中寻找最大值。每次都保证前N个数最大的一个数在第N-1个位置,直到数组最后。
  • 最大的数就像气泡一样,依次冒到最后的位置。

时间复杂度O(n^2),空间复杂度O(1)

属于稳定的排序算法。



void bubbleSort(vector<int>&arr)

{

    for (int i = 0; i < arr.size() - 1; i++){

        for (int j = 0; j < arr.size() - i -1; j++) {

            if(arr[j] > arr[j+1])

                swap(arr, j, j+1);

            }

        }

    }

}

 

void swap(vector<int>&arr, int i, int j){

    int tmp = arr[i];

    arr[i] = arr[j];

    arr[j] = tmp;

}

2.选择排序

选择排序是一种简单直观的的排序算法。基本思路和冒泡排序看似一样。

  • 数组从左到右,两两比较,但不进行交换,使用一个变量记录最大值所在数组的下标,向左依次移动,直到末尾
  • 将最大值与数组末尾交换
  • 在从剩余未排序元素中继续寻找最大值,放到排序好序列的前一个位置

时间复杂度O(n^2),空间复杂度O(1)

属于不稳定的排序算法


注意:动图中是选择的最小的往前排,和我介绍的思路一样。

void selectionSort(){
    int length = sizeof(arr) / sizeof(arr[0]);
    if(length < 0)
        return;
    for(int i = 0; i < length; ++i){
        int minValue = arr[i];
        int index = i;
        for(int j = i + 1; j < length; ++j){
            if(minValue > arr[j]){
                index = j;
                minValue = arr[j];
            }
        }
        if(index != i){
            swap(arr, index, i);
        }
    }
}

void swap(vector<int> &arr, int i, int j){
    int tmp = arr[i];
    arr[i] = arr[j];
    arr[j] = tmp;
}

3.插入排序

人如其名,算法也是。将一个个数依次插入到已经排序好的数组中,这是插入排序的思路。

  • 数组中前n个数已经排序好,将第n+1个数M插入到已排序好的数组中
  • 将M与前一个数比较,如果M小,则与前一个数交换
  • 依次与前一个数进行比较,直到M大于等于前一个数,停止,排序完成

时间复杂度O(n^2),空间复杂度O(1)


void insertSort(){
    int length = sizeof(arr) / sizeof(arr[0]);
    if(arr == NULL || length < 2)
        return;
    for(int i = 0; i < length; ++i){
        for(int j = i - 1; j >= 0 && arr[j] > arr[j+1]; --j) {
            swap(arr, j, j+1);
        }
    }
}

void swap(vector<int> &arr, int i, int j){
    int tmp = arr[i];
    arr[i] = arr[j];
    arr[j] = tmp;
}

4.归并排序

归并排序采用的是分而治之的思想。有位伟人说过,大事化小小事化了。开个玩笑!其实就是将一个大问题转化成一个个小问题,小问题解决,大问题也就解决了。

  • 将一个未排序的数组分为两个子数组
  • 两个子数组分别排好序,然后合并成一个有序的数组

那么两个子数组怎么排好序呢?没错!在分别分为两个子子数组,然后一直分一直分一直分...直到只有一个数,那么这一个数肯定是排好序的,然后向上合并,一直合并一直合并,直到原始数组大小。那么这个排序的关键点就在怎么合并成一个有序数组。

两个数组a和b,分别都已经排好序

准备一个辅助数组c

a[0] 与 b[0]进行比较,较小数(假如是[a0]较小)放在c[0]位置,a向左移动一个位置

a[1] 与 b[0]比较,依次进行,直到有一个数组比较完

* 另一个数组剩下的数拷贝到c数组中

时间复杂度O(nlogn),空间复杂度O(n)

属于稳定的排序算法

void mergeSort(){
    int length = sizeof(arr) / sizeof(arr[0]);
    if(arr == NULL || length < 2)
        return;
    mergeMain(arr, 0, length);
}

//用的是递归
void mergeMain(int arr[], int L, int R){
    if(L == R)
        return;
    int mid = L + ((R - L) >> 1);
    mergesort(arr, L, mid);
    mergesort(arr, mid + 1, R);
    merge(arr, L, mid, R);
    }

void merge(int arr[], int l, int m, int r){
    int help[l-r+1];
    int i = 0;
    int p1 = l;
    int p2 = m + 1;
    while (p1 <= m && p2 <= r) {
        help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
    }
    while (p1 <= m) {
        help[i++] = arr[p1++];
    }
    while (p2 <= r) {
        help[i++] = arr[p2++];
    }
    for(int i = 0; i < l - r + 1; i++){
        arr[i + l] = help[i];
    }
}

5.快速排序

 

简称快排。C++的库函数中有快排的函数,可见快排是挺靠谱的。基本思路就是,将数组分为两个部分,前一部分比某个数M要小,另一部分比M要大,分别对这两个部分依次进行这样的操作,最后达到整个数组的排序。

掌握快排要掌握两个关键点:

  • 第一个关键点:怎么寻找这个数M,寻找方法的好坏之间关系到排序算法的时间复杂度。
  • 第二个关键点:怎么将一个数组小的放在左边,大的放在右边,等于的放在中间。

先介绍第二个关键点的问题,其实就是荷兰国旗问题。


现有红白蓝三个不同颜色的小球,乱序排列在一起,请重新排列这些小球,使得红白蓝三色的同颜色的球在一起。

这个问题之所以叫荷兰国旗问题,是因为我们可以将红白蓝三色小球想象成条状物,有序排列后正好组成荷兰国旗。

我们将这个问题转化成排序问题,即红白蓝分为对应数字0,1,2,那么就是小于1的放在左边,大于1的放在右边,等于1的放在中间。

/*
在arr上的l到r位置,小于num的放在左边,大于num的放在右边,等于num的放在中间
less代表小于边界,more代表大于边界
有3种情况需要考虑,具体解释看下一个代码
*/
void NetherlandFlag(vector<int> &arr, int num, int l, int r){
    if(l >= r){
        return;
    }
    int less = l - 1;
    int more = r + 1;
    int cur = l;
    while(cur < more){
        if(arr[cur] < num){
            swap(arr, ++less, cur++);
        }
        else if(arr[cur] > num){
           swap(arr, --more, cur);
        }
        else{
            cur++;
        }
    }
}

void swap(vector<int> &arr, int i, int j){
    int tmp = arr[i];
    arr[i] = arr[j];
    arr[j] = tmp;
}

关于第一个关键点,其实就是随机选择一个数,选择数的好坏,将会影响排序的时间复杂度。

时间复杂度O(n*logn),空间复杂度O(logn)

属于不稳定的排序算法


/*
划分函数,类似于荷兰国旗问题
有3个指针,less代表小于的边界,more代表大于的边界,cur代表现在的位置,代码中用L代替
当前数<num时,less右边一个位置和当前位置交换,并同时向右移动一个位置
当前数>num时,more左边一个位置和当前位置交换,more向左移动一个位置,当前位置不变
当前书=num时,当前位置向右移动一个位置
最终,小于num的在左边,大于num的在右边,等于num的在中间
*/
int* partition(vector<int> &arr, int L, int R){
    int less = L - 1;
    int more = R;
    while(L < more){
        if(arr[L] < arr[R]){
            swap(arr, ++less, L++);
        }
        else if(arr[L] > arr[R]){
            swap(arr, --more, L);
        }
        else{
            L++;
        }
    }
    int *p = new int[2];
    swap(arr, L, R);
    p[0] = less;
    p[1] = more + 1;
    return p;
}

/*
递归函数
随机找到数组中一个数作为划分点
取得随机整数[a, b]函数为 rand()%(b-a+1)+a
*/
void sort(vector<int> &arr, int L, int R){
    swap(arr, rand() % (R-L+1) + L, R);
    if(L < R){
        int* p = new int[2];
        p = partition(arr, L, R);
        sort(arr, L, p[0]);
        sort(arr, p[1], R);
    }
}

void swap(vector<int> &arr, int i, int j){
    int tmp = arr[i];
    arr[i] = arr[j];
    arr[j] = tmp;
}

6.堆排序

要弄清楚什么叫堆排序,首先要理解什么叫堆,什么叫大根堆,小根堆。

堆是一个完全二叉树,其实堆是一个概念,而不是一个物理结构,物理结构可以用数组,来表达堆。

要记住两个关键点:

  • 节点下标i的左孩子节点下标是(2×i+1),右孩子节点下标是(2×i+2)
  • 节点下标i的父节点下标是(i-1)/2

时间复杂度O(n*logn),空间复杂度O(1)

属于不稳定的排序算法


/*
堆排序算法
首先把数组调整成大根堆(注:大根堆只是逻上的概念,并不是一个确定的数据结构)
定义大根堆尺寸,即heapSize
将堆顶元素(即数组为0的位置)与大根堆最一个元素(即数组最后一个位置)交换,尺寸一,heapSize--
此时,堆并不是一个大根堆,重新调整为大根堆
依次循环进行,直到heapSize<=0,排序结束
*/
void heapSort(vector<int> &arr){
    int length = arr.size();
    if(arr == NULL || length < 2){
        return;
    }
    for(int i = 0; i < length; i++)
    {
        heapInsert(arr, i);
    }
    swap(arr, 0, --length);
    while(length > 0){
        heapify(arr, 0, length);
        swap(arr, 0, --length);
    }
    
}
/*
大根堆插入元素之后,重新调整为大根堆的函数
*/
void heapInsert(vector<int> &arr,int index){
    while(arr[index] > arr(index-1)/2]){
        swap(arr, index, (index-1/2);
        index = (index-1) / 2;
    }
}
/*
将堆顶元素弹出,重新调节为大根堆的函数
*/
void heapify(vector<int> &arr, intindex, int heapSize){
    int left = 2 * index + 1;
    while(left < heapSize){
        int largest = left+1 <heapSize && arr[left] < ar[left+1] ? left+1: left;
        largest = arr[index] < ar[largest] ? largest :index;
        if(largest == index){
            break;
        }
        swap(arr, index, largest);
        index = largest;
        left = 2 * index + 1;
    }
}
void swap(vector<int> &arr, int i,int j){
    int tmp = arr[i];
    arr[i] = arr[j];
    arr[j] = tmp;
}

个人声明

     由于本人水平有限,也是刚学习,其中不免出现错误,还请各位看官不吝赐教,万分感谢!

     而且本篇也算是学习笔记,是看视频上课的知识点

参考

  牛客网左程云算法初级课

  十大经典排序算法

猜你喜欢

转载自blog.csdn.net/weixin_39953502/article/details/79589211