算法专题----程序员必须知道的8大排序和3大查找

来源:互联网 发布:director.js 编辑:程序博客网 时间:2024/05/16 08:21

一、冒泡排序

基本思路:

冒泡排序是一种简单的交换类排序。其基本思路是,从头开始扫描待排序的元素,在扫描过程中依次对相邻元素进行比较,将关键字值大的元素后移。每经过一趟排序后,关键字值最大的元素将移到末尾,此时记下该元素的位置,下一趟排序只需要比较到此位置为止,直到所有元素都已有序排列。

一般地,对n个元素进行冒泡排序,总共需要进行n-1趟。第1趟需要比较n-1次,第2趟需要比较n-2次,......第i趟需要比较n-i次。

算法程序实现

public class BubbleSort{
 public static void main(String args[]){
    int[] values={3,1,6,2,9,0,7,4,5};
    sort(values);//调用下面封装好的sort方法,并把参数传递给sort方法,因为sort方法需要传递进来参数
    for(int i=0;i<values.length;i++){//排序后打印数组中的元素
      System.out.println("Index: "+i+"  value: "+values[i]);
    }
  }
  public static void sort(int[] values){
    int temp;
    for(int i=0;i<values.length;i++){//趟数
      for(int j=0;j<values.length-i-1;j++){//比较次数
        if(values[j]>values[j+1]){
          temp=values[j];
          values[j]=values[j+1];
          values[j+1]=temp;
        }
      }
    }
  }
}

运行结果如下:

二、选择排序

选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理如下:首先在未排序序列中找到最大(小)元素,存放到该待排序序列的最后(起始)位置,然后,再从剩余未排序元素中继续寻找最大(小)元素,然后放到已排序序列的前面(末尾)。以此类推,直到所有元素均排序完毕。

SelectSor.java

package Test7;
public class SelectSort {
public static void main(String[] args) {
//定义一组数组
int array[] =new int[]{63,4,24,1,3,15};
System.out.println("array数组的长度为:"+array.length);
//创建SelectSort对象
SelectSort selectst=new SelectSort();
//通过SelectSort对象调用sort()方法。注意:这里不能直接调用sort()方法,是因为
selectst.sort(array);
}
public void sort(int array[]){
//n个数需要排n-1趟,这里array.length=5,所以5个数需要排4趟
for(int i=1;i<array.length;i++){
int index=0;
//把第一个数(索引i=0)选做被比较的数,从第二个数(i=1)开始比较,注意要比较到最后
//所以添加上=号,又因为每比较完一次就少比较一个数,所以-i。

for(int j=1;j<=array.length-i;j++){
//我们先假定第一个数(索引为0)最大,如果后面的数比被比较的数大,那么改变最大数的
//索引,一直比较到最后。

if(array[j]>array[index]){
index=j;
}
}
//上面比较完后就会找到这一趟中最大数的索引,那么交换该最大数到后面相应的位置
int temp=array[array.length-i];
array[array.length-i]=array[index];
array[index]=temp;
}
//循环打印排好序的数组

System.out.println("排序后数组为:");
for(int i=0;i<array.length;i++){
System.out.print(" "+array[i]);
}
        }

}

界面显示结果如图:


/*总结:1、
 * 排序前【63 4 24 1 3 15】
 * 第1趟 【15 4 24 1 3】63
 * 第2趟 【15 4 3 1】24 63
 * 第3趟 【1 4 3】15 24 63
 * 第4趟 【1 3】4 15 24 63
 * 第5趟 【1】3 4 15 24 63
 * 可以看出n个数 需要排n-1趟   
 *2、这种排序问题,特别特别要注意的地方是i这种循环变量,从几开始的,又到几,是小于还是小于等于,一定
 *  搞清楚,不然就会严重影响结果。比如我们这个例子中,外循环中i是从1开始的,循环四次就要i<5。又因
 *  为我们数组索引从0开始,所以数组最后一个数的索引为:数组长度-1,这样在交换变量的时候就一定得注意
 *  写为array[array.length-i]表示是数组的最后一个数。
 *  在内循环中,最重要的是为什么(j<=array.length-i)中有个减去i,这是因为我们第1趟时选中了第
 *  一个数最为被比较的数,剩下的四个数都是需要跟第一个数来比较一下的。第2趟的时候,我们依然把第一个数
 *  做为被比较的数,但剩下的四个数中最后一个数已经排好了,它就是最大的了,无需再拿它来跟第一个数比较了
 *  所以这时有剩下的三个数是需要跟第一个数比较的,依次类推我们可以得出,每比较完一趟就少比较一个数,而
 *  这个i是每比较一趟就加1,所以用减去i刚好。
 *  又因为我们已经选定了第一个数(索引i=0)做为比较的数,所以从(索引i=1)开始跟第一个数比 较,一共5个
 *  数,那么这里为了比较到最后一个数就要写为(<=array.length-i),如果写为(<array.length-i)
 *  就到不了最后,如果写为(<array.length)无法到达内循环中每比较完一次就少比较一个数。
 */

直接选择排序的复杂度:
选择排序的交换操作介于0和(n-1)次之间。选择排序的比较操作为n(n-1)/2次。选择排序的赋值操作介于03n-1)次之间。
比较次数O(n2),比较次数与关键字的初始状态无关,总的比较次数N=(n-1)+(n-2)+...+1=n*(n-1)/2。 交换次数O(n),最好情况是,已经有序,交换0次;最坏情况是,逆序,交换n-1次。交换次数比冒泡排序少多了,由于交换所需CPU时间比比较所需的CPU时间多,n值较小时,选择排序比冒泡排序快。
小结:
适合的数据结构:数组
最差时间复杂度:O(n2)
最优时间复杂度:O(n2)
平均时间复杂度:O(n2)
最差空间复杂度:总共O(n),需要辅助空间O(1)

三、直接插入排序

基本思想:在要排序的一组数中,假设前面(n-1) [n>=2] 个数已经是排好顺序的,现在要把第n个数插到前面的有序数中,
使得这n个数也是排好顺序的。如此反复循环,直到全部排好顺序。

InsertSort.java

package port;
// 直接插入排序
public class InsertSort {
public static void main(String[] args){
int array[]={9,13,5,8,25,4};
sort(array);
System.out.println("排序后的数组为:");
for(int i=0;i<array.length;i++){
System.out.print(array[i]+" ");
}
}
public static void sort(int array[]){
for(int i=1;i<array.length;i++){
//从第一个元素(索引为0)开始,该元素可以被认为已经排序,那取出下一个元素,在已经排序的元素序列中从后向前扫描
//比如:我们的第一个元素(索引为0)为9,那么认为它已经排好序,取出下一个元素(索引为1)为13,

for(int j=i;j>0;j--){
//判断array[1]=13是否小于array[0]=9,不则再去下一个元素。下个元素为array[2]=5,那么从
//排好序的9,13这个序列后面开始跟5比较,即先比较13和5,再比较9和5,

if(array[j]<array[j-1]){
int temp=array[j];
array[j]=array[j-1];
array[j-1]=temp;
}
}
}
}
}

程序运行结果:


九、反转排序

ReverseSort.java

package Test7;
/*
 * 反转排序的思想:就是把数组第一个元素和最后一个元素交换,第二个和倒数第二个交换,依次类推直到把所有数组
 * 元素交换完。反转排序是对数组两边的元素进行交换,所以只需要交换数组长度一半的次数。如果数组长度为7,那么
 * for循环只需要循环3次。
 */
public class ReverseSort {
public void sort(int array[]){
int len=array.length;
//交换数组两边对应位置的数
for(int i=0;i<len/2;i++){
int temp=array[len-i-1];
array[len-i-1]=array[i];
array[i]=temp;
}
//排序后的数组为:
System.out.println();
System.out.println("反转排序后的数组为:");
for(int i=0;i<array.length;i++){
System.out.print(" "+array[i]);
}
}
public static void main(String[] args) {
//定义一组数组
int array[]=new int[]{10,20,30,40,50,60};
//排序前的数组为
System.out.println("反转排序前的数组为:");
for(int i=0;i<array.length;i++){
System.out.print(" "+array[i]);
}
//实例化ReverseSort方法
ReverseSort reseverst=new ReverseSort();
//调用sort()方法
reseverst.sort(array);
}
}


最后:排序算法的稳定性分析

现在我们分析一下8种排序算法的稳定性。

(请网友结合前面的排序基本思想来理解排序的稳定性(8种排序的基本思想已经在前面说过,这里不再赘述)不然可能有些模糊)

(1)直接插入排序:一般插入排序,比较是从有序序列的最后一个元素开始,如果比它大则直接插入在其后面,否则一直往前比。如果找到一个和插入元素相等的,那么就插入到这个相等元素的后面。插入排序是稳定的。

(2)希尔排序:希尔排序是按照不同步长对元素进行插入排序,一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,稳定性就会被破坏,所以希尔排序不稳定。

(3)简单选择排序:在一趟选择,如果当前元素比一个元素小,而该小的元素又出现在一个和当前元素相等的元素后面,那么交换后稳定性就被破坏了。光说可能有点模糊,来看个小实例:858410,第一遍扫描,第1个元素8会和4交换,那么原序列中2个8的相对前后顺序和原序列不一致了,所以选择排序不稳定。

(4)堆排序:堆排序的过程是从第n/2开始和其子节点共3个值选择最大(大顶堆)或者最小(小顶堆),这3个元素之间的选择当然不会破坏稳定性。但当为n/2-1, n/2-2, ...这些父节点选择元素时,有可能第n/2个父节点交换把后面一个元素交换过去了,而第n/2-1个父节点把后面一个相同的元素没有交换,所以堆排序并不稳定。

(5)冒泡排序:由前面的内容可知,冒泡排序是相邻的两个元素比较,交换也发生在这两个元素之间,如果两个元素相等,不用交换。所以冒泡排序稳定。

(6)快速排序:在中枢元素和序列中一个元素交换的时候,很有可能把前面的元素的稳定性打乱。还是看一个小实例:6 4 4 5 4 7 8  9,第一趟排序,中枢元素6和第三个4交换就会把元素4的原序列破坏,所以快速排序不稳定。

(7)归并排序:在分解的子列中,有1个或2个元素时,1个元素不会交换,2个元素如果大小相等也不会交换。在序列合并的过程中,如果两个当前元素相等时,我们把处在前面的序列的元素保存在结果序列的前面,所以,归并排序也是稳定的。

(8)基数排序:是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序,最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,所以是稳定的。

8种排序的分类,稳定性,时间复杂度和空间复杂度总结:




0 0