数据结构与算法之简单排序

本章重点:

如何排序?

计算机程序却不能像人这样通览所有的数据。它只能根据计算机的“比较”操作原理,在同一时间内对两个队员进行比较。算法的这种“管视”将是一个反复出现的问题。在人类看来很简单的事情,计算机的算法却不能看到全景,因此它只能一步一步地解决具体问题和遵循一些简单的规则。

这一章的三个算法都包括如下的两个步骤,这两步循环执行,直到全部数据有序为止:

1.比较两个数据项。

2.交换两个数据项,或复制其中一项。

但是,每种算法具体实现的细节有所不同。

冒泡排序

冒泡排序算法运行起来非常慢,但在概念上它是排序算法中最简单的,因此冒泡排序算法在刚开始研究排序技术时是一个非常好的算法。

冒泡排序的Java代码

在更复杂的程序中,数据很可能由对象组成在,这里为了简单起见,使用的是基本数据类型。

//bubbleSort.java

class ArrayBub{
    private long[] a;              //ref to array a
    private int nElems;            //number of data items
//--------------------------------------------------------
    public ArrayBub(int max){      //constructor
        a = new long[max];         //create the array
        nElems = 0;                //no items yet
    }
//--------------------------------------------------------
    public void insert(long value){          //put element into array
        a[nElems] = value;
        nElems++
    }  
//--------------------------------------------------------
    public void display(){                    //displays array contents
        for(int j=0;j<nElems;j++){
            System.out.println(a[j]+" ")
        }
    }
//--------------------------------------------------------
    public void bubbleSort(){
        int out,in;
        for(out=0;out<nElems;out++){
            for(in=0;in<nElems-1;in++){
                if(a[in]>a[in+1]){
                    swap(in,in+1);
                }
            }
        }
    }
//--------------------------------------------------------
    private void swap(int one,int two){
        long temp = a[one];
        a[one]    = a[two];
        a[two]    = temp;
    }
}

class BubbleSortApp{
    public static void main(String[] args){
        int maxSize = 100;
        ArrayBub    arr;
        arr  = new ArrayBub(maxSize);

        arr.insert(77);
        arr.insert(99);
        arr.insert(44);
        arr.insert(55);

        arr.display();

        arr.bubbleSort();
        arr.display();
    }
}

这个类的构造函数,以及insert()和display()方法与见过的那些方法是类似的,但是这里多了一个新方法:bubbleSort()。当main()调用这个方法时,数组中的数据就会排列成有序。

不变性

在许多算法中,有些条件在算法执行时是不变的。这些条件被称为不变性。认识不变性对理解算法是有用的。在一定的情况下它们对调试也有用;可以反复地检查不变性是否为真,如果不是的话就标记出错。

在bubbleSort.java程序中,不变性是指out右边的所有数据项为有序。在算法的整个运行过程中这个条件始终为真。(在第一趟排序开始前,尚未排序,因为out开始时再数据项的最右边,没有数据项在out的右边。)

冒泡排序的效率

比较的次数:(N-1)+(N-2)+(N-3)+(N-4)+(N-5)+(N-6)+(N-7)+(N-8)+(N-9)+...+1=N*(N-1)/2,交换的次数少于比较的次数。如果数据是随机的,那么大概有一半数据需要交换,则交换的次数为N2/4。

交换和比较操作次数都和N2成正比。由于常数不算在大O表示法中,可以忽略2和4,并且认为冒泡排序运行需要O(N2)时间级别。这种排序算的速度是很慢的。

无论何时,只要看到一个循环嵌套在另一个循环里,例如在冒泡排序和本章中的其他排序算法中,就可以怀疑这个算法的运行时间为O(N2)级。外层循环执行N次,内部循环对于每一次外层循环都执行N次(或者几分之N次)。这就意味着将大约需要执行N*N某个基本操作。

 

选择排序

选择排序改进了冒泡排序,将必要的交换次数从O(N2)减少到O(N)。不幸的是比较次数仍然保持为O(N2)。然而,选择排序仍然为大记录量的排序提出了一个非常重要的改进,因为这些大量的记录需要在内存中移动,这就使交换的时间和比较的时间相比起来,交换的时间更为重要。

class ArraySel{
    private long[] a;
    private int nElems;
//-----------------------------------------------------
    public ArraySel(int max){    //constructor
            a = new long[max];
            nElems = 0;
    }
//-----------------------------------------------------
    public void insert(long value){       //put element into array
        a[nElems] = value;                //insert it
        nElems++;                         //increment size   
    }
//-----------------------------------------------------
    public void display(){                 //displays array contents
        for(int j=0;j<nElems;j++){         //for each element
            System.out.print(a[j]+" ");    //display
        }
    }
//-----------------------------------------------------
    public void selectionSort(){
        int out,in,min;

        for(out=0;out<nElems;out++){
            min = out;
            for(in=out+1;in<nElems;in++){
                if(a[in]<a[min]){
                    min = in;
                }
            }
            swap(out,min);
        
     }
//-----------------------------------------------------
   public vodi swap(int one ,int two) {
        long temp = a[one]        ;
        a[one] = a[two];
        a[two] = temp;
   }
}

class SelectSortApp{
    public static void main(String[] args){
        int maxSize = 100;
        ArraySel arr;
        arr = new ArraySel(maxSize);

        arr.insert(77);
        arr.insert(78);
        arr.insert(67);

        arr.display();

        arr.selectionSort();
        arr.display();
    }
}

不变性:

在selectSort.java程序中,下标小于或等于out的位置的数据项总是有序的。

选择排序的效率:

选择排序和冒泡排序执行了相同次数的比较:N*(N-1)/2。对于10个数据项,需要45次比较。然而,10个数据项只需要小于10次交换。对于100个数据项,需要4950次比较,但是交换就会大大降低。与冒泡下相比,比较次数运行了O(N2)时间,但是交换次数大大降低,无疑,选择排序更快。

插入排序

在大多数情况下,插入排序算法是本章描述的基本的排序算法中最好的一种,虽然插入排序算法仍然需要O(N2)的时间,但是在一般情况下,它要比冒泡排序快一倍,比选择排序还要快一点。尽管它比冒泡排序算法和选择算法都更麻烦,但它也并不复杂。它经常被用在较复杂的排序算法的最后阶段,例如快速排序。

package com.thinkofdatastruct.threeunit;

/**
 * Created by happy on 2019/04/04
 */

public class insertionSort {

    /*
     * 直接插入排序
     *
     * 参数说明:
     *     a -- 待排序的数组
     *     n -- 数组的长度
     */
    public static void insertSort(int[] a, int n) {
        int i, j, k;

        for (i = 1; i < n; i++) {

            //为a[i]在前面的a[0...i-1]有序区间中找一个合适的位置
            for (j = i - 1; j >= 0; j--){
                if (a[j] < a[i])
                    break;
            }

            //如找到了一个合适的位置
            if (j != i - 1) {
                //将比a[i]大的数据向后移
                int temp = a[i];
                for (k = i - 1; k > j; k--){
                    a[k + 1] = a[k];
                }
                //将a[i]放到正确位置上
                a[k + 1] = temp;
            }
        }
    }

    public static void main(String[] args) {
        int[] a = {20,40,30,10,60,50};

        insertSort(a, a.length);

        for (int i=0; i<a.length; i++)
            System.out.printf("%d ", a[i]);
 
    }
}

插入排序中的不变性

这个算法需要多少次比较和复制呢?在第一趟排序中,它最多比较一次,第二趟最多比较两次,依次类推。最后一趟最多,比较N-1次。因此有

1+2+3+....+N-1 = N*(N-1)/2

然而,因为在每一趟排序发现插入点之前,平均只有全体数据项的一半真的进行了比较,我们除以2得到

N*(N-1)/4

复制的次数大致等于比较的次数。然而,一次复制与一次交换的时间耗费不同,所以相对于随机数据,这个算法比冒泡排序快一倍,比选择排序略快。

对于数据越有序,就越快,几乎接近O(N);

对象排序

为简单起见,前面应用排序算法时使用的是基本数据类型:long(长整型)。但是,排序例程却更多地应用于对象的排序,而不是对基本数据类型的排序。下面是对一组Person对象进行排序的代码。

class Person{
    private String lastName;
    private String firstName;
    private int age;

//-----------------------------------------------------
    public Person(String last,String first,int a){    //constructor
        lastName     = last;
        firstName    = first;
        age          = a;
    }
//-----------------------------------------------------
    public void displayPerson(){
        System.out.print(" Last name: "+lastName);
        System.out.print(" ,First name: "+firstName);
        System.out.print(" ,Age: "+age);
    }
//-----------------------------------------------------
    public String getLast(){
        return lastName;
    }
}

class ArrayInOb{
    private Person[] a;
    private int nElems;
//-----------------------------------------------------
    public ArrayInOb(int max){    //constructor
        a = new Person[max];
        nElems=0;
    }
//-----------------------------------------------------
    public void insert(String last,String first,int age){
        a[nElems] = new Person(last,first,age);
        nElems++;
    }
//-----------------------------------------------------
    public void display(){
        for(int i=0;i<a.length;i++){
            a[j].displayPerson();
        }
    }
//-----------------------------------------------------插入排序
    public void insertionSort(){
        int in,out,k;
        for(out=1;out<nElems;out++){
            for(in=out-1;in>=0;in--){
                if(a[in].getLast.compareTo(a[out].getLast())>0){
                    break;
                }
            }
            if(out!=in-1){
                Person temp = a[in];
                for(k=in-1;k>out;k++){
                    a[k+1] = a[k];
                }
                a[k+1] = temp;
            }
        }
    }
}

class ObjectSortApp{
    public static void main(String[] args){
        int maxSize = 100;
        ArrayInOb arr;
        arr = new ArrayInOb(maxSize);
        arr.insert("E","P",24);
        arr.insert("Ee","Pp",244);
        arr.insert("Eee","Ppp",144);

        arr.display();
        arr.insertionSort();
    }
}

单词排序:

objectSort.java程序中的insertSort()方法和insertSort.java中的方法类似,只是不再比较基本数据类型,而是比较记录中的lastName(姓)属性。

用String类中的compareTo()方法执行insertSort()方法中的比较工作。compareTo()方法根据两个String的字典顺序(字母序)返回给调用者不同的整数值,这两个String一个是方法的调用者,一个是这个方法的参数。

 

简单排序的比较

冒泡排序算法一般情况几乎不太使用;

选择排序虽然把交换次数降到了最低,但比较的次数仍然很大。当数据量很小,并且交换数据相对于比较数据更加耗时的情况下,可以应用选择排序;

在大多数情况下,假设当数据量比较小或基本上有序时,插入排序算法是三种简单排序算法中最好的选择。对于更大数据量的排序来说,快速排序通常是最快的方法;

小结:

本章提到的排序算法都假定了数组作为数据存储结构;

排序包括比较数组中数据项的关键字和移动相应的数据项(实际上,是数据项的引用),直到它们排好序为止;

本章所有算法的时间复杂度都是O(N2)。不过,某些情况下某个算法可以比其它算法快很多;

不变性是指在算法运行时保持不变的条件;

冒泡排序算法是效率最差的算法,但它最简单;

插入排序算法是本章介绍的O(N2)排序算法中应用最多的;

如果具有相同关键字的数据项,经过排序它们的顺序保持不变,这样的排序就是稳定的;

本章介绍的所有排序算法除了需要初始数组之外,都只需要一个临时变量。

问题:

1.计算机排序算法与人类排序相比较,它的局限性是:计算机一次只能比较两件东西。

2.简单排序算法中的两个基本操作是 比较数据项和交换数据项。

3.冒泡排序算法总是在所有数据项两两比较完成后停止;错误

4.冒泡排序算法在比较和交换之间交替进行。

5.有N个数据项,冒泡排序算法精确操作了N*N次; 错误

6.选择排序中 b。

    a.最大的关键字聚集到左边(较小的下标);b.最小的关键字被重复发现;

    c.为了将每个数据项插入到正确排序的位置,很多数据项将被移动;d.有序的数据项聚集到右边。

7.在某一个特定的排序情况中,如果交换与比较相比费时得多,那么选择排序将比冒泡排序快大约一倍。错误

8.复制是交换得三倍;

9.选择排序中不变性是什么?

    排完序的下标小于outer的值。

10.插入排序中,文中描述的“被标记的队员”对应于insertSort.java中的哪个变量? 答案是c  a.in;b.out;c.temp;d.a[out]

11.在插入排序中,“局部有序”是指:一些数据项已经排好序了,但它们可能需要被移动。

12.向左或右移动一组数据项需要重复地 复制。

13.在插入排序中,一个数据项被插入到局部有序的组合后,它将:永远不会向左边移动。

14.插入排序中的不变性是 排完序的下标小于outer的下标。

15.稳定性是指 在对州进行排序时,每个州的城市还要求按人口递增有序。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

京河小蚁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值