【算法】排序

来源:互联网 发布:分类汇总数据复制出来 编辑:程序博客网 时间:2024/06/06 05:23

在已排序的数组中插入一个元素

我们先看下面这个问题。
对于给定的数组
这里写图片描述

我们要在其中插入k(n< k < n+1)元素有两种方法

从左到右:
k 和0比较,k比0大,不操作;
k和1比较,k比1大,不操作;
……
k和n比较,k比n大,不操作;
k和n+1比较,k比n+1小,把n+1之后的所有元素向后一移动一位然后然后把k写入arr[n+1]中

代码如下

void insert(int* arr, int length, int beInsertedNum) {    int left = 0;    while (left<length&&arr[left] < beInsertedNum) {        left++;    }    int right = length-2;    while (right>=left) {        arr[right + 1]= arr[right];        right--;    }    arr[left] = beInsertedNum;}

从右到左:
k先和n+N(右边0左边的一个数)比较,k小于n+N,所以arr[n+N+1]=n+N;
……
k和n比较,k比大,所以把k写入到arr[n+1]中
代码如下

void insert(int* arr, int length, int beInsertedNum) {    int right = length-2;    while (right>=0&&beInsertedNum < arr[right]) {        arr[right + 1] = arr[right];        right--;    }    arr[right] = beInsertedNum;}

上面两种方法的时间复杂度都是O(n)。但是下面的代码跟好看一些。现在我们写一个完全版的,

void insert(int* arr,int length,int beInsertedNum,bool smallToBig) {    if (smallToBig) {        int right = length - 2;        while (right >= 0 && beInsertedNum < arr[right]) {            arr[right + 1] = arr[right];            right--;        }        arr[right] = beInsertedNum;    }else{        int right = length - 2;        while (right >= 0 && beInsertedNum > arr[right]) {            arr[right + 1] = arr[right];            right--;        }        arr[right] = beInsertedNum;    }}

当然上面的算法例子是一个教学案例,只对插入一个元素使用。局限性很大。而且在效率上也很慢。但是对于之后的讲排序已经够了。我们会在之后不断完善这个算法

Tips:
对于已经排序完毕的数列来说,二分法查找某个元素的速度比上面的算法要快得多(log2(n))
代码实现

void binary_search(int * arr, int size, int X) {    mergSortS(arr, 0, size - 1);    int head = 0;    int end = size-1;    int  mid = size / 2;    int current;    for (int i = 0; i < size; i++) {         current = X-arr[i];         for (; head <=end;) {             if (current == arr[mid]) {                 repeat = true;                 return;             }             if (current < arr[mid])                  end = --mid;             else                 head = ++mid;             mid = (head + end )/ 2;         }    }}

插入排序

插入排序,是对一个混乱的数组进行排序的算法。

算法思想:
先排序前两个或或后个元素的大小,比较完毕之后就产生了一个排列好的小数组,然后我们依次向小数组中插入剩下的值,直到全部插入完毕

例如

int arr[6]={5,2,4,6,1,3};

从小到大排序。
我们先排序5,2,得到{2,5}的小数组;
把4插入到{2,5}中,得到{2,4,5};
……
依次完插入6,1,3之后最终的到{1,3,4,5,6}
代码如下

void insertion_sort(int * arr, int length ) {    int left, right_value;    for (int right = 1; right < length; right++) {        right_value = arr[right];        left = right - 1;        //在大数组中找到待插入的数,right++表示从左边向右        //-----------------------        //把找到的数插入到小数组中        while (left >= 0 && arr[left] > right_value) {            arr[left + 1] = arr[left];            left--;        }        arr[left + 1] = right_value;        //-------------------    }}

现在我们来写一个比较全面的代码

void insertion_sort(int * arr,int length,bool smallToBig ) {    if (smallToBig) {        for (int right = 1; right < length; right++) {            int left, right_value;            right_value = arr[right];            left = right - 1;            while (left>=0&&arr[left] > right_value) {                arr[left + 1] = arr[left];                left--;            }            arr[left + 1] = right_value;        }    }else {        for (int right = 1; right < length; right++) {            int left, right_value;            right_value = arr[right];            left = right - 1;            while (left>=0 && arr[left] < right_value) {                arr[left + 1] = arr[left];                left--;            }            arr[left + 1] = right_value;        }    }}

Tips:
可以看见插入排序的核心就是向一个已排序的数列中不停得插入新的元素,所以我们可以在寻找元素的时候使用二分法来查找

冒泡算法

算法思想:
按照某种规则交换相邻的两个元素,这样最左边的元素最终会被交换到最右边(最右边的元素最终会被交换到最左边),这样一个元素被排序完成了,之后在排序系啊一个元素

例如

int arr[6]={5,2,4,6,1,3};

从小到大排序。
先交换比较5,2 得到{2,5,4,6,1,3};
再比较5,4得到{2,4,5,6,1,3};
接着比较5,6,没有变化还是{2,5,4,6,1,3};
再比较6,1,得到{2,5,4,1,6,3};
最后比较6,3。得到{2,5,4,1,3,6};
第一轮完成,6被交换到最右边了,这样就排序好了最大的数。
……

实现代码

void bubble_sort(int* arr, int length, bool smallToBig) {    int temp;    if (smallToBig) {        for (int  right_offset= length ; right_offset > 0; right_offset--) {            for (int left = 0, right = left + 1; right < right_offset; left++,right++) {                if (arr[left] > arr[right]) {                    temp = arr[right];                    arr[right] = arr[left];                    arr[left] = temp;                }            }        }    }    else {        for (int right_offset = length; right_offset > 0; right_offset--) {            for (int left = 0, right = left + 1; right < right_offset; left++, right++) {                if (arr[left] < arr[right]) {                    temp = arr[right];                    arr[right] = arr[left];                    arr[left] = temp;                }            }        }    }}

插入排序和冒泡算法的时间复杂度是O(n^2)。

归并排序

归并排序的思想是分治法,再代码实现上用到了递归操作。

算法思想:
归并排序的核心思想是分治法,如果一个问题庞大到一定程度使我们不能有效快速解决,我们可以把问题划分成若干个较小的问题,分头解决,并把各自结果合并。

例如:

int arr[6]={5,2,4,6,1,3};

我们可以按照下面方法来做

这里写图片描述

我们一层一层把问题分解到足够小的子集,然后再排序每层的子集,最后讲结果合并。

说真的,对我来是,这个算法很反直觉。我们之后来说。先看看代码的实现

#include "stdafx.h"#include<iostream>#include<climits>using namespace std;void merge_sort(int *arr ,int l,int m,int r) {    int l_length = m - l + 1;//l~m    int r_length = r - m;//m+1~r    int* l_arr = new int[l_length+1];    int* r_arr = new int[r_length+1];    for (int i = 0; i < l_length; i++)        l_arr[i] = arr[l + i ];    for (int i = 0; i < r_length; i++)        r_arr[i] = arr[m+1 + i];    l_arr[l_length] = INT_MAX;    r_arr[r_length] = INT_MAX;    int i = 0;    int j = 0;    for (int k = l; k <= r; k++) {        if (l_arr[i] <= r_arr[j]) {            arr[k] = l_arr[i];            i++;        }else{            arr[k] = r_arr[j];            j++;        }    }    cout << endl;    delete[]l_arr;    l_arr = nullptr;    delete[]r_arr;    r_arr = nullptr;}void merge(int* arr, int l, int r) {    if (l<r) {//保证递归不是无限的        int m = (l + r) / 2;        cout << l << "|" << m << "|" << r << endl;        merge(arr, l, m);//l~m        merge(arr, m+1, r);//m+1~r        merge_sort(arr, l, m, r);    }}int main(){    int arr[6] = { 5,2,4,6,1,3 };    merge(arr, 0, 5);    cout << "-----------------"<< endl;    for (int  i = 0; i <6; i++)    {        cout << arr[i] << endl;    }    system("pause");    return 0;}

merge_sort负责合并两个较小的数组,merge递归的把任务拆分为两个较小的两个任务。

优点:递归排序的优点是效率高(O(nlogn),底按以分治法的复杂度而定)。
缺点:空间复杂度比较高O(n),比较占中内存

现在我们来聊聊这个算法反直觉的地方,可以看见上面算法会把任务细分了再处理。这就产生了很多递归细分任务的时间,所以当被排序的元素足够少的情况下实际效率不一定比时间复杂度为O(n^2)的快。再来说一说被排序的元素比较多的情况下。由于元素很多且我们不考虑空间(内存)因素,所以我们可以忽略任务分配的时间。把重点放在被细分任务的处理和结果合并上。因为画图太麻烦了,所以我们就口头说一说,不懂得可以自行百度。拿二路归并来说。每一层的任务会是上一层的2倍。但是单个任务的处理时间缩短为原来的一半。这样每一层的时间复杂度都是一样的。因为是2^n分配的。会有log2(n)层,时间复杂度就会变成O(nlog2(n))

【参考】

http://www.cnblogs.com/gaochundong/p/comparison_sorting_algorithms.html#bubble_sort

《算法导论第三版》

原创粉丝点击