排序算法总结

  今天来总结一下排序算法。排序算法有多种多样,而且也是面试中常见的考察点。排序算法分别有冒泡排序、插入排序,选择排序、希尔排序、归并排序、快排以及堆排序。
  首先是冒泡排序,冒泡排序原理简单,每次都是两两比较,然后把大的放在后面,一直比较,直到把前M个元素中最大的放在最后面。也就是说经过一趟排序,最大的元素处在最后一位,再经过一趟排序,除最后一个元素之外的最大元素在倒数第二位。
  1、从头开始元素两两比较,如果x>y,则交换x和y。
  2、接着上面最大元素的位置继续和后面的元素比较,直到把最大的元素放在最后。
  3、重复执行上面的两个步骤。
当然在经过一趟排序之后,最大的元素已经在最后了,在后面的排序中就不用考虑最后的那个元素了。
  代码如下:

void bubbleSort(int* arr, int length){
    if (arr== nullptr)
        return;

    for (int i=0; i<length-1; i++){       // 第几趟
        for (int j=0; j<length-i-1; j++){ // 两两交换
            if (arr[j]>arr[j+1]){
                int tmp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = tmp;
            }
        }
    }
}

时间复杂度为:O(n^2)
空间复杂读为:O(n)
上述算法也可以把交换数据的代码单独拿出来,写成一个函数来调用。
  第二是插入排序,插入排序就是从第一个元素开始,把它和它前面的元素逐个比较,直到找到比它小的元素,把它放在比它小的元素的后面。
  1、从第一个位置取出元素get,把get和它前面的元素逐个进行比较。
  2、直到找到比get的小的元素,把get放在比它小的元素的后面。
  3、重复上述步骤,直到最后一个元素。
代码如下:

void InsertionSort1(vector<int>& a){
    for (int i=1; i<a.size(); i++){//从第1个位置开始,每一个元素当一次get
        int get = a[i];
        int j = i-1;
        while (j>=0 && a[j]>get){//和get前面的元素逐个比较
            a[j+1] = a[j];
            j--;
        }
        a[j+1] = get;  //把get放在比它小的元素后面
    }
}

注意:当把get放在选定位置时,应该是j+1, 因为当找到比get小的元素时,执行了一次j–

时间复杂度为:O(n^2)
空间复杂度为:O(n)

  第三个为选择排序,选择排序是从当前乱序的数组中找出一个最小的元素,放在有序数组的最后面。
  1、从头开始遍历数组,找到数组中最小的元素,把这个元素与第一个元素交换。
  2、再从第二个元素开始遍历,找到最小的一个元素,把这个元素与第二个元素交换。
代码如下:

void selectionSort(vector<int>& a){
    for (int i=0; i<a.size()-1; i++){
        int min = i;

        for (int j=i+1; j<a.size(); j++){
            if (a[j]<a[min])
                min = j;
        }
        int tmp = a[i];
        a[i] = a[min];
        a[min] = tmp;

    }
}

时间复杂度为:O(n^2)
空间复杂度为:O(n)

  第四个是希尔排序从名字上看不出来特点,因为它是以发明者命名的。它的另一个名字是“递减增量排序算法“。这个算法可以看作是插入排序的优化版,因为插入排序需要一位一位比较,然后放置到正确位置。为了提升比较的跨度,希尔排序将数组按照一定步长分成几个子数组进行排序,通过逐渐减短步长来完成最终排序。
代码如下:

void shell_sort(vector<int> &nums) {
    for (int gap = nums.size() >> 1; gap > 0; gap >>= 1) { // times
        for (int i = gap; i < nums.size(); i++) { // position
            int temp = nums[i];
            int j = i - gap;
            for (; j >= 0 && nums[j] > temp; j -= gap) {
                nums[j + gap] = nums[j];
            }
            nums[j + gap] = temp;
        }
    }
}

  第五个是归并排序,归并排序其实就是一个分治的思路,把数组对半分,逐渐分到每一个数组只有一个元素的时候,再数组中两两比较,然后合并数组,变成每一个数组有两个元素的情况,依次类推即可。
代码如下:

void Merge(int arr[], int reg[], int start, int end) {
    if (start >= end)return;
    int len = end - start, mid = (len >> 1) + start;

    //分成两部分
    int start1 = start, end1 = mid;
    int start2 = mid + 1, end2 = end;
    //然后合并
    Merge(arr, reg, start1, end1);
    Merge(arr, reg, start2, end2);

    int k = start;
    //两个序列一一比较,哪的序列的元素小就放进reg序列里面,然后位置+1再与另一个序列原来位置的元素比较
    //如此反复,可以把两个有序的序列合并成一个有序的序列
    while (start1 <= end1 && start2 <= end2)
        reg[k++] = arr[start1] < arr[start2] ? arr[start1++] : arr[start2++];

    //然后这里是分情况,如果arr2序列的已经全部都放进reg序列了然后跳出了循环
    //那就表示arr1序列还有更大的元素(一个或多个)没有放进reg序列,所以这一步就是接着放
    while (start1 <= end1)
        reg[k++] = arr[start1++];

    //这一步和上面一样
    while (start2 <= end2)
        reg[k++] = arr[start2++];
    //把已经有序的reg序列放回arr序列中
    for (k = start; k <= end; k++)
        arr[k] = reg[k];
}

void MergeSort(int arr[], const int len) {
    //创建一个同样长度的序列,用于临时存放排序后的子树组
    int  reg[len];
    Merge(arr, reg, 0, len - 1);
}

时间复杂度为:O(nlgn)
空间复杂度为:O(n)

  第六个是快速排序,快速排序也是一种分治的思路,但是它并不是把数组分成一半一半的,而是从数组中选择一个基准数,然后经过一趟遍历数组,比基准数小的全部在一侧,比基准数大的全部在另一侧。然后再分别在基准数两侧的数组上实现排序。
第一种方法,是左右两个指针一块向中间动,代码如下:

void swap(int& num1, int& num2){
    int tmp = num1;
    num1 = num2;
    num2 = tmp;
}

void quickSort1(int arr[], int left, int right){
    if (left<right){
        int key = arr[left], low = left+1, high = right;

        while (low<=high){
            while (low<high && arr[low]>key && arr[high]<key)
                swap(arr[left++], arr[high--]);

            if (arr[low]<=key) low++; // 此处不需要low<high如果加上了这句,永远跳不出while循环
            if (arr[high]>=key) high--;   
        }
        swap(arr[left], arr[high]);
        quickSort1(arr, left, high-1);
        quickSort1(arr, high+1, right);
    }
}

上面有两处需要注意的地方,第一个是最外层的while循环,是low<=high,还有就是最后交换的是arr[left]和arr[high],因为此时high所指向的位置一定是小于key值的,low所指向的位置一定是大于key值的。所以我们要叫交换的就是arr[left]和arr[high]。
另一中写法比较简单,也是两个指针,只是这两个指针一个一个的动。代码如下:

void quickSort2(int arr[], int left, int right){
    if (left<right){
        int key = arr[left], low=left, high = right;
        while (low<high){
            while (low<high && key<=arr[high])
                high--;
            if (low<high)
                arr[low++] = arr[high];

            while (low<high && key>=arr[low])
                low++;
            if (low<high)
                arr[high--] = arr[low];
        }
        arr[low] = key;
        quickSort2(arr, left, low-1);
        quickSort2(arr, low+1, right);
    }
}

  第七种为堆排序,堆排序其实是基于二叉树来操作的。按照层序遍历二叉树的顺序,以二叉树的最后一个非叶子节点开始,调整该节点及其子节点,使得该节点的元素值大于其两个子节点。然后再按照由下往上的层序遍历顺序,对上一个非叶子节点进行这样的调整,直到调整完根节点。这样整个二叉树都满足,根节点大于子节点的值。
  接着再交换根节点和最后一个叶子节点的值,这样最大值就放在了最后。因为我们调整了二叉树的根节点,所以还有对二叉树进行调整,使其满足根节点大于叶子节点的情况。调整完,再把根节点和倒数第二个节点交换,以此类推即可。
代码如下:

void heapAdjust(int arr[], int beg, int length){//堆调整
    int left = 2*beg+1;
    while (left<length){
        if (left+1<length && arr[left]<arr[left+1])
            left++;
        if (arr[beg]<arr[left]){
            swap(arr[beg], arr[left]);
            beg = left;
            left = 2*beg+1;
        }
        else
            break;
    }
}


void heapSort(int arr[], int length){
    for (int i=length/2-1; i>=0; i--){
        heapAdjust(arr, i, length-1);
    }

    for (int i=length-1; i>0; i--){
        swap(arr[0], arr[i]);
        heapAdjust(arr, 0, i);
    }
}

参考:

https://www.cnblogs.com/MOBIN/p/5374217.html
http://www.runoob.com/w3cnote/sort-algorithm-summary.html
http://yansu.org/2015/09/07/sort-algorithms.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值