算法笔记(二)——排序算法

冒泡排序

冒泡排序相对来说是较为简单的一种排序,它的思想就是在每一次循环中比较相邻的两个数,通过交换的方式,将最小的元素或最大的元素交换到指定位置,举个例子:

将 50 34 19 18 47 按从小到大排序

  • 首先比较50与34,如果前者大于后者,那么交换两个数的位置,以便让大的数慢慢移到后面,因此交换50与34;
  • 比较50与19,大的移到后面,因此交换;
  • 当比较到第5次时,47已经是最后一个数了,交换50与47后,第一次循环就结束了,这时候找到了最大的元素50,并将50放在了最右的位置,50已经排好序了。
  • 继续从头开始比较,如果满足条件就交换,直到第8次比较,这时47是最后一个未排序的数,因此此次循环到这里就结束了,因为50已经排好序没必要再比较了;
  • 重复以上过程直至全部排序完成。

整个排序过程如下:


观察排序过程我们发现:

  1. 对于a这个数组,长度为5,只要进行4次循环比较就能完成任务,(1234)为第一个循环,(567)为第二个,(89)为第三个,(10)为第四个,因此外循环的次数等于数组长度减1。
  2. 内循环下标每次从0开始,到a.length-i-1结束,i为外循环循环变量(从0开始)。

由以上两点就可以写出代码了:

public void bubbleSort(int[] array){
        for (int i = 0; i < array.length-1; i++) {
            for (int j = 0; j < array.length-1-i; j++) {
                if(array[j] > array[j+1]){
                    //swap函数用于交换数组中下标为i和j的两个元素
                    swap(array,j,j+1);
                }
            }
        }
    }

选择排序

选择排序的思想是,每次循环找到当前循环的最大或者最小的元素,并将它与指定位置的元素进行交换。

下图表示一个数组选择排序的过程

  • i=0时,当前最小的元素应该被放置在下标0的位置(紫色),于是在下标从0到9中找出最小的元素,找到的最小值为4(红色),将最小值与紫色位置进行交换,此时4已经归位;
  • i=1时,此时当前最小元素应该被放置在下标1的位置(紫色),于是在下标1到9中找出最小元素,找到的最小值为6(红色),将下标1与6位置的元素进行交换,此时4与6都已经排序完成;
  • 重复上述过程直到所有元素均排序完成。

由以上过程可以发现选择排序的特点:

  1. 外循环的次数为数组的长度(a.length)。
  2. 每次循环中,需要在下标从i到a.length-1之间找出最小值。
  3. 交换最小值与下标i位置的元素。

由此可以写出代码:

public static void selectSort(int[] a) {
        for (int i = 0; i < a.length; i++) {
            //记录当前循环中最小元素的下标
            int minIndex = i;
            for (int j = i; j < a.length; j++) {
                if (a[j] < a[minIndex]) {
                    minIndex = j;
                }
            }
            //交换最小值与下标i位置的元素
            swap(a, minIndex, i);
        }
    }

选择排序改进

既然选择排序的时候是每次找最小值,然后交换,那么反正找最小值的过程要遍历一个数组,能不能在找最小值的过程中顺便将最大值也一起找了呢?然后将最小值放在最前面,最大值放在最后面,不就实现了一个循环排好两个元素了吗?

改进的排序算法过程如下:

i为循环次数,min、max分别代表当前循环中最小值与最大值的下标。这里为了看的更清楚,所以将交换最小值与最大值分别画出来了,实际上找最大值与最小值是在一次循环中完成的。

但是在交换最大值与最小值的过程中有一个细节需要注意,如果出现了这种情况:

2和87已经排好序了,那么交换了最小值后(将4与45交换),这时再交换最大值时就会出错,此时max指向的元素已经被交换了,此时最大值的位置应该在min指向的位置,因此交换最大值的时候实际交换的应该是15和min指向的元素。

由此可以写出改进后的代码:

public static void selectSortImpv(int[] a) {
    //循环次数为原先的一半
    for (int i = 0; i < a.length / 2; i++) {
        //最小元素的下标
        int minIndex = i;
        //最大元素的下标
        int maxIndex = i;
        for (int j = i + 1; j < a.length - i; j++) {
            if (a[j] < a[minIndex]) {
                minIndex = j;
            }
            if (a[j] >= a[maxIndex]) {
                maxIndex = j;
            }
        }
        //交换最小元素
        Utils.swap(a, minIndex, i);
        if (maxIndex == i) {
            swap(a, a.length - i - 1, minIndex);
        } else {
            swap(a, a.length - i - 1, maxIndex);
        }
        printArray(a);
    }
}

插入排序

插入排序的思想跟扑克牌理牌的方法一样,比如跟朋友玩斗地主的时候,扑克牌全部发完后,每个人拿自己的一摞牌理牌,回想一下我们是怎么理牌的,先抽出第一张放在手中,再抽出第二张,如果比第一张小就放在第一张的左边,否则放在右边,再抽出第三张牌,看它适合放在哪两张牌之间(或者最左边最右边)就插入进去,重复这样的过程直到抽完所有牌,这样自己的牌就排序好了。

还是以之前的数组排序为例看一下插入排序的过程:

  • 首先第一个数不需要排序,从第二个数开始,34比40小,那么需要在34前面的位置(下标为0,红色区域)将34插入进去
  • 那么先将34提出来(黑色方框),然后把40往后移一位,这样空出了①位置,这个位置就是34应该放置的位置,因此把34放在该位置。
  • 然后看第三个元素19,19比前一个元素40小,因此需要在19前面(下标从0到1,红色区域)找到19应该插入的位置。
  • 将19提出来(黑色方框),然后把40往后移,空出了②位置。
  • 19再与前面一个值34比较,19小与34,所以19不能放在②位置,将34完后移一位,空出了③位置,这时19放在该位置是符合要求的,因此把19放在该位置。
  • 重复以上步骤直到最后一个元素插入完成。

插入排序的过程简单来说就是:

  1. 第一个元素不需要排序,循环从1开始。
  2. 如果当前元素a[i]比前一个元素大,那么不需要变动位置。
  3. 如果当前元素a[i]比前一个元素小,那么将当前元素提出来,并在这个元素前(下标从0到i-1)找到一个合适插入位置j。
  4. 将下标j与i-1之间(包括i-1)的元素统统往后移一个位置,以便腾出一个空间。
  5. 将当前元素放入腾出的空间a[j]中。

以下是一个数组插入排序的完整过程:

插入排序的代码如下:

public static void insertSort(int[] a){
        //第一个元素不用操作,循环从1开始
        for (int i = 1; i < a.length; i++) {
            //如果当前元素大于前一个元素,那么已经是有序的,不作处理
            if(a[i] > a[i- 1]){
                continue;
            }else{
                //将当前元素取出
                int temp = a[i];
                //在下标为0到i-1中查找该元素应该插入的位置
                for (int j = 0; j < i; j++) {
                    if(a[j] >= temp){
                        //查找到插入位置为下标j,将下标j到i的元素往后移位,以把下标j位置空出来
                        for (int k = i; k > j ; k--) {
                            a[k] = a[k-1];
                        }
                        //将当前元素放在下标j位置
                        a[j] = temp;
                        break;
                    }
                }
            }
        }
    }

这里找插入位置使用的是从左到右顺序查找,即从0查找到i-1,如果中间有满足的位置那么查找结束,可以使用二分查找进行优化。

希尔排序

希尔排序是一种基于插入排序的排序算法。对于大规模的数组,插入排序很慢,因为它需要进行大量的移位操作,元素只能一点一点地从数组一端移动到另一端。我们知道插入排序对内部有序的数组来说效率是非常高的,所以希尔排序的思想将数组内部逐渐变得有序,这样为插入排序创造一个好的条件,再使用插入排序的时候就能省去很多移位操作。

先来看一个概念:

h有序数组:数组中任意间隔h的元素都是有序的。举个例子,h=3的时候,假设数组a长度为8,那么如果a[0],a[3],a[6]是有序的,a[1],a[4],a[7]是有序的,a[2],a[5]是有序的,就可以称a数组是3有序数组

下面以一个例子看看希尔排序的过程:

  • 数组的长度为10,如果先将h设为5(数组长度一半)的话,那么可以将数组拆分成5个长度为2的子数组(第一个框框,黑色部分),分别对这5个子数组进行插入排序,得到排序后的子数组(第一个框框,红色部分)。
  • 子数组排序后,原数组就变成了5有序数组了(第二个框框第一行),这时候缩小h,将h设置为2(原来h的一半),那么可以将原数组拆分成2个长度为5的数组(第二个框框,黑色部分),对这两个子数组再使用插入排序,得到排序后的子数组(第二个框框,红色部分)。
  • 经过上一步,原数组已经是2有序数组了(第三个框框,第一行),继续缩小h,将h设置为1,其实就是对原数组直接进行插入排序了,这样排序完成后,整个数组就排序完成了(第三个框框,红色部分)。不用再缩小h了(这不是废话嘛= =)。

结合上述过程再看代码就比较清晰了,代码如下:

public static void shellSort(int[] a) {
    //刚开始h为数组长度一半
    int h = a.length / 2;
    //如果h<1的话,结束循环,数组已经排序完成
    while (h >= 1) {
        //使用插入排序对子数组进行排序
        shellInsert(a, h);
        //将h的值缩小一半
        h = h / 2;
    }
}
private static void shellInsert(int[] a, int h) {
    //分别对每个子数组操作,总共有h个子数组
    for (int i = 0; i < h; i++) {
        //每个子数组进行插入排序
        for (int j = i+h; j < a.length; j += h) {
            int temp = a[j];
            int k = j - h;
            //找到当前元素应该被插入的位置j
            for (; k >= i; k-=h) {
                if(a[k] >= temp) {
                    a[k + h] = a[k];
                }else{
                    break;
                }
            }
            //将当前元素放入插入位置
            a[k + h] = temp;
        }
    }
}

像上述h的取值为1,2,5这样的序列叫递增序列,选择合适的递增序列有助于提升希尔排序的效率,我看的算法书中建议使用1,4,13,40,121,364……这样的递增序列,公式为h=3*h+1

归并排序

归并排序是典型的分治思想,将一个数组分成两个排序过的数组,然后对这两个数组进行归并就能得到排序后的数组。而两个子数组又可以通过这样的方式继续拆分下去,直到每个数组中只有一个元素。

归并

归并的功能是,将两个有序数组合并成一个有序数组。比如下面的例子:

红色为第一个数组,绿色为第二个数组,两个数组内的元素都是从小到大排列,怎么将这样两个数组合并成一个大的数组?

  • 新建一个用于保存最终结果的临时数组temp,长度为两个数组的长度之和;
  • 使用两个指针,i指向第一个数组首元素18,j指向第二个数组首元素4,比较a[i]和b[j]的大小;
  • b[j]小,那么把b[j]放入临时数组中temp[0]的位置,此时b数组中4已经添加过了,将j指向下一个元素24(j++),再次比较a[i]和b[j]大小。
  • a[i]小,那么把a[i]放入临时数组中temp[1]的位置,此时a数组中18已经添加过了,将i指向下一个元素19(i++),再次比较a[i]和b[j]大小。
  • a[i]小,那么把a[i]放入临时数组中temp[2]的位置,此时a数组中19已经添加过了,将i指向下一个元素34(i++),再次比较a[i]和b[j]大小。
  • 重复以上过程,如果碰到某一个数组中的元素已经全部添加完了(i==a.length || j==b.length),那么将另一个数组的剩下的元素直接添加到temp后面,比如上图倒数第二行,此时a中元素已经添加完了,b中还剩两个元素,那么直接将42,47放到temp[5],也就是40后面。
/**
 * 归并操作
 * @param a     数组
 * @param left  第一个子数组首元素下标
 * @param mid   第一个子数组尾元素下标
 * @param right 第二个子数组尾元素下标
 */
private static void merge(int[] a, int left, int mid,
    //临时数组用于保存合并后的数组
    int[] temp = new int[right - left +1];
    //指向左边数组的指针
    int first  = left;
    //指向右边数组的指针
    int second = mid+1;
    //临时数组的指针
    int tempIndex = 0;
    //如果两个数组指针都没有出界,即两个数组中都还有未添加的元素
    while(first <= mid && second <= right) {
        if (a[first] <= a[second]) {
            temp[tempIndex] = a[first];
            tempIndex++;
            first++;
        } else {
            temp[tempIndex] = a[second];
            second++;
            tempIndex++;
        }
    }
    //如果一个数组已经全部加入到临时数组中,另一个数组剩下的部分直接放到临时数组后
    if(first == (mid+1)){
        for(;second<= right;second++){
            temp[tempIndex] = a[second];
            tempIndex++;
        }
    }
    if(second == (right+1)){
        for(;first<= mid;first ++){
            temp[tempIndex] = a[first];
            tempIndex++;
        }
    }
}

自底向上的归并排序

自底向上的归并排序的思想可以看下图:

  1. 如果一个数组中只有一个元素的话,那么它肯定是有序的(这也是废话,一个元素何来排序)。所以我们将每个元素看成一个长度为1的数组,这样就能用归并操作对这两个数进行排序了。因此将原数组两两一组,分别进行归并,如果最后剩下一个就不用归并了,这样得到的数组如标号1的那行所示。
  2. 经过上一步,数组中已经红色部分和绿色部分都是已经排好序的了,那么再讲这些排好序的部分两两一组进行归并,就会得到标号2那行的数组,此时红色和绿色部分已经是排好序了。可以看到归并操作正在将有序的部分逐渐扩大。
  3. 重复以上步骤,直到合并的两个数组形成的新数组长度正好等于原数组的时候,原数组就排序好了。

对于一个具体的数组来说,归并的过程的执行次序如下:

自顶向下的归并排序

自顶向下的归并需要用到递归的思想,对于一个未排序的数组,我们想对它进行排序,这个排序函数应该怎么调用?

首先先确定参数形式:

sort(int[] a,int left,int right)

a为数组,left,right分别为要排序的开始下标和结束下标。

那么递归的边界条件是什么?我们知道如果数组中只有一个元素就不用排序了,因此如果left与right相等,那么就不用再递归调用了,直接退出就好了。所以完成代码如下:

private static void sort(int[] a,int left,int right){
    if(left == right){
        return ;
    }
}

然后对于一般情况,我们要做的是什么?联系一下归并函数merge的参数,我们想到,将一个数组分成两半,对这两半分别进行排序,再将排序后的数组合并。由此写出下面的代码:

private static void sort(int[] a,int left,int right){
    if(left == right){
        return ;
    }
    int mid = (left + right) / 2;
    //对左半边进行排序
    sort(a,left,mid);
    //右半边
    sort(a,mid+1,right);
    //将左右两个数组合并
    merge(a,left,mid,right);
}

归并排序的全部代码:

public static void mergeSort(int[] a){
    sort(a,0,a.length-1);
}
private static void sort(int[] a,int left,int right){
    if(left == right){
        return ;
    }
    int mid = (left + right) / 2;
    //对左半边进行排序
    sort(a,left,mid);
    //右半边
    sort(a,mid+1,right);
    //将左右两个数组合并
    merge(a,left,mid,right);
}
/**
 * 归并操作
 * @param a     数组
 * @param left  第一个子数组首元素下标
 * @param mid   第一个子数组尾元素下标
 * @param right 第二个子数组尾元素下标
 */
private static void merge(int[] a, int left, int mid, int right) 
    //临时数组用于保存合并后的数组
    int[] temp = new int[right - left +1];
    //指向左边数组的指针
    int first  = left;
    //指向右边数组的指针
    int second = mid+1;
    //临时数组的指针
    int tempIndex = 0;
    while(first <= mid && second <= right) {
        if (a[first] <= a[second]) {
            temp[tempIndex] = a[first];
            tempIndex++;
            first++;
        } else {
            temp[tempIndex] = a[second];
            second++;
            tempIndex++;
        }
    }
    //如果一个数组已经全部加入到临时数组中,另一个数组剩下的部分直接放到临时数组后
    if(first == (mid+1)){
        for(;second<= right;second++){
            temp[tempIndex] = a[second];
            tempIndex++;
        }
    }
    if(second == (right+1)){
        for(;first<= mid;first ++){
            temp[tempIndex] = a[first];
            tempIndex++;
        }
    }
    //将临时数组的值赋给原数组
    for (int i = 0; i < temp.length; i++) {
        a[i+left] = temp[i];
    }
}

以一个数组为例,程序的执行流程如下:

函数执行顺序 合并示意图

可以看到对于每次递归调用sort都是先排序左边,再排序右边,再合并左边与右边。

快速排序

快速排序也是一种分治算法,它也是将一个数组分成左右两个数组,将两部分独立地排序。快速排序跟归并排序的区别是:归并排序是先将数组分成两个子数组,对两个子数组分别排序后再合并。快速排序省去了归并排序的合并操作,当两个子数组有序的时候,整个数组就有序了;在归并排序中,递归调用发生在处理数组之前,也就是上文中sort函数在merge函数之前,而快速排序中,递归调用发生在处理整个数组之后,这个咱们之后联系代码再看;归并排序是将数组等分成两半,再快速排序中,切分(partition)的位置取决于数组的内容。

切分

切分实现的是这样的功能,选定一个基准元素(后直接称pivot),将数组中不大于pivot的都放到它的左边,不小于pivot的放到它的右边。以下面的图为例,pivot取的是首元素40(紫色),进行切分后,40左边红色的部分都是不大于40的,40右边绿色的部分都是不小于40的。如果红色与绿色部分分别都是有序的,那么整个数组就是有序的了(第三行)。


下面来看看切分的具体步骤:

  1. 使用一个左指针(红色)和右指针(绿色),左指针指向pivot的下一个,右指针指向最后一个元素。
  2. 左指针从左到右找第一个不小于pivot的元素(47),右指针从右往左找第一个不大于pivot的元素(11)。
  3. 如果两个指针没有相遇或者移到边界,那么交换两个元素(蓝色)。
  4. 如果两个指针相遇或是移到边界,那么结束循环,最后将pivot与右指针指向的元素互换(蓝色)。

为什么不是左指针而是右指针呢?这个与选的pivot有关,如果选的是最左边的元素,那么就是右指针;如果pivot选的是最右边的元素,那么交换的就是左指针了。从图中也能看出来,左右指针相遇时,左指针指向的是42,右指针指向的是24,如果是交换42和40明显就不对了。

下面是切分的示意图:

下面给出切分的代码,对照切分的步骤应该不难理解:

public static int partition(int[] a,int left,int right){
    //取最左边的元素作为基准元素
    int pivotValue = a[left];
    //左指针
    int i = left+1;
    //右指针
    int j = right;
    while (true) {
        //左指针从左到右找第一个不小于pivot的元素
        while (a[i] <= pivotValue) {
            //指针移到边界
            if(i == right){
                break;
            }
            i++;
        }
        //右指针从右往左找第一个不大于pivot的元素
        while (a[j] > pivotValue) {
            //指针移到边界
            if(j == left){
                break;
            }
            j--;
        }
        //左右指针相遇
        if (i>= j){
            break;
        }
        //交换左右指针指向的元素
        swap(a,i,j);
    }
    //最后交换右指针与pivot
    swap(a,left,j);
    //返回值为pivot所在的下标位置
    return j;
}

快速排序过程

了解了切分的过程后,快排的思想就出来了,同归并一样,考虑递归的执行过程。对于一个数组,要先进行切分,将数组切分成左右两个数组,再分别对左右两个数组进行排序,而这个排序又是先切分,再对左右进行排序。于是我们能写出这样的代码:

public static void sort(int[] a,int left,int right){
    if(left>=right){
        return ;
    }
    //返回排序后pivot所在下标位置
    int pivot = partition(a,left, right);
    //对pivot左边元素进行排序
    sort(a,left,pivot-1);
    //对pivot右边元素进行排序
    sort(a,pivot+1,right);
}

对于一个具体的例子,快速排序的示意图与流程如下:

快排示意图 实际执行顺序

下面是快速排序的完整代码:

/**
 * 快速排序,选取最左边元素作为基准元素
 * @param a
 */
public static void quickSort(int[] a){
    sort(a,0,a.length-1);
}
public static void sort(int[] a,int left,int right
    if(left>=right){
        return ;
    }
    //返回排序后pivot所在下标位置
    int pivot = partition(a,left, right);
    //对pivot左边元素进行排序
    sort(a,left,pivot-1);
    //对pivot右边元素进行排序
    sort(a,pivot+1,right);
}
public static int partition(int[] a,int left,int r
    //取最左边的元素作为基准元素
    int pivotValue = a[left];
    //左指针
    int i = left+1;
    //右指针
    int j = right;
    while (true) {
        //左指针从左到右找第一个不小于pivot的元素
        while (a[i] <= pivotValue) {
            //指针移到边界
            if(i == right){
                break;
            }
            i++;
        }
        //右指针从右往左找第一个不大于pivot的元素
        while (a[j] > pivotValue) {
            //指针移到边界
            if(j == left){
                break;
            }
            j--;
        }
        //左右指针相遇
        if (i>= j){
            break;
        }
        //交换左右指针指向的元素
        swap(a,i,j);
    }
    //最后交换右指针与pivot
    swap(a,left,j);
    //返回值为pivot所在的下标位置
    return j;
}

参考资料

  1. 《算法》
  2. 面试中的排序算法总结
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容

  • 概述 排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    蚁前阅读 5,101评论 0 52
  • 概述:排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    每天刷两次牙阅读 3,704评论 0 15
  • 总结一下常见的排序算法。 排序分内排序和外排序。内排序:指在排序期间数据对象全部存放在内存的排序。外排序:指在排序...
    jiangliang阅读 1,265评论 0 1
  • 1.插入排序—直接插入排序(Straight Insertion Sort) 基本思想: 将一个记录插入到已排序好...
    依依玖玥阅读 1,174评论 0 2
  • 文/王诗文 第一桶金,尽管说起来简单,真要达成却并不容易,尤其对于年轻人而言,更是难上加难。然而在朝目标持续努力的...
    王诗文circle阅读 1,693评论 26 38