排序算法

概述(原创文章:https://blog.csdn.net/happy_wu/article/details/51841244)

排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。

我们这里说说八大排序就是内部排序。

当n较大,则应采用时间复杂度为O(nlog2n)的排序方法:快速排序、堆排序或归并排序序。

   快速排序:是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短

1.插入排序—直接插入排序(Straight Insertion Sort)

基本思想:

将一个记录插入到已排序好的有序表中,从而得到一个新,记录数增1的有序表。即:先将序列的第1个记录看成是一个有序的子序列,然后从第2个记录逐个进行插入,直至整个序列有序为止。

要点:设立哨兵,作为临时存储和判断数组边界之用。

直接插入排序示例:



如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。

算法的实现:

package sortPractice;  
  
  
public class InsertSort {    
   
    public static void main(String[] args) {    
        int a[] = {3,1,5,7,2,4,9,6,10,8};      
        InsertSort  obj=new InsertSort();    
        System.out.println("初始值:");    
        obj.print(a);    
        obj.insertSort(a);    
        System.out.println("\n排序后:");    
        obj.print(a);    
    
    }    
    
    public void print(int a[]){    
        for(int i=0;i<a.length;i++){    
            System.out.print(a[i]+" ");    
        }    
    }    
    public void insertSort(int[] a){
        int i,j,t=0;
        for(i=1;i<a.length;i++){//从头部第一个当做已经排好序的,把后面的一个一个的插到已经排好的列表中去。    
                t=a[i];//x为待插入元素    
                for( j=i-1;  j>0 && x<a[j];j--){//通过循环,逐个后移一位找到要插入的位置。 
                     a[j+1]=a[j]; 
                   a[j]=t;//插入
        }  
}
 }

//对代码进行优化(时间复杂度O(n^2)) 

2. 插入排序—希尔排序(Shell`s Sort)

希尔排序是1959 年由D.L.Shell 提出来的,相对直接排序有较大的改进。希尔排序又叫缩小增量排序

基本思想:

先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。

操作方法:

  1. 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
  2. 按增量序列个数k,对序列进行k 趟排序;
  3. 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

希尔排序的示例:


算法实现:

我们简单处理增量序列:增量序列d = {n/2 ,n/4, n/8 .....1} n为要排序数的个数

即:先将要排序的一组记录按某个增量dn/2,n为要排序数的个数)分成若干组子序列,每组中记录的下标相差d.对每组中全部元素进行直接插入排序,然后再用一个较小的增量(d/2)对它进行分组,在每组中再进行直接插入排序。继续不断缩小增量直至为1,最后使用直接插入排序完成排序。


package com;
/*
 * Java实现希尔排序(缩小增量排序)
 * author:wyr
 * 2016-7-14
 *两个步骤:1,建堆  2,对顶与堆的最后一个元素交换位置
 */
public class ShellSort {

	public static void main(String[] args) {
		int a[] = {3,1,5,7,2,4,9,6,10,8};  
		ShellSort  obj=new ShellSort();
		System.out.println("初始值:");
		obj.print(a);
		obj.shellSort(a);
		System.out.println("\n排序后:");
		obj.print(a);

	}
	private void shellSort(int[] a) {
		 int dk = a.length/2; 
		 while( dk >= 1  ){  
	        ShellInsertSort(a, dk);  
	        dk = dk/2;
		 }
	}
	private void ShellInsertSort(int[] a, int dk) {//类似插入排序,只是插入排序增量是1,这里增量是dk,把1换成dk就可以了
		for(int i=dk;i<a.length;i++){
			if(a[i]<a[i-dk]){
				int j;
				int x=a[i];//x为待插入元素
				a[i]=a[i-dk];
				for(j=i-dk;  j>=0 && x<a[j];j=j-dk){//通过循环,逐个后移一位找到要插入的位置。
					a[j+dk]=a[j];
				}
				a[j+dk]=x;//插入
			}
				
		}
		
	}
	public void print(int a[]){
		for(int i=0;i<a.length;i++){
			System.out.print(a[i]+" ");
		}
	}
}

希尔排序时效分析很难,关键码的比较次数与记录移动次数依赖于增量因子序列d的选取,特定情况下可以准确估算出关键码的比较次数和记录的移动次数。目前还没有人给出选取最好的增量因子序列的方法。增量因子序列可以有各种取法,有取奇数的,也有取质数的,但需要注意:增量因子中除1 外没有公因子,且最后一个增量因子必须为1。希尔排序方法是一个不稳定的排序方法


3. 选择排序—简单选择排序(Simple Selection Sort)

基本思想:

在要排序的一组数中,选出最小(或者最大)的个数与第1个位置的数交换;然后在剩下的数当中再找最小(或者最大)的与第2个位置的数交换,依次类推,直到第n-1个元素(倒数第二个数)和第n个元素(最后个数)比较为止。

简单选择排序的示例:

 

操作方法:

第一趟,从n 个记录中找出关键码最小的记录与第一个记录交换;

第二趟,从第二个记录开始的n-1 个记录中再选出关键码最小的记录与第二个记录交换;

以此类推.....

第i 趟,则从第i 个记录开始的n-i+1 个记录中选出关键码最小的记录与第i 个记录交换,

直到整个序列按关键码有序。

算法实现:

package com;
/*
 * Java实现希尔排序(缩小增量排序)
 * author:wyr
 * 2016-7-14
 *两个步骤:1,建堆  2,对顶与堆的最后一个元素交换位置
 */
public class SimpleSelectSort {

	public static void main(String[] args) {
		int a[] = {3,1,5,7,2,4,9,6,10,8};  
		SimpleSelectSort  obj=new SimpleSelectSort();
		System.out.println("初始值:");
		obj.print(a);
		obj.selectSort(a);
		System.out.println("\n排序后:");
		obj.print(a);

	}
	private void selectSort(int[] a) {
		for(int i=0;i<a.length;i++){
			int k=i;//k存放最小值下标。每次循环最小值下标+1
			for(int j=i+1;j<a.length;j++){//找到最小值下标
				if(a[k]>a[j])
					k=j;
			}
			swap(a,k,i);//把最小值放到它该放的位置上
		}
	}
	public void print(int a[]){
		for(int i=0;i<a.length;i++){
			System.out.print(a[i]+" ");
		}
	}
	 public  void swap(int[] data, int i, int j) {  
	        if (i == j) {  
	            return;  
	        }  
	        data[i] = data[i] + data[j];  
	        data[j] = data[i] - data[j];  
	        data[i] = data[i] - data[j];  
	    }  
}

 简单选择排序的改进——二元选择排序

简单选择排序,每趟循环只能确定一个元素排序后的定位。我们可以考虑改进为每趟循环确定两个元素(当前趟最大和最小记录)的位置,从而减少排序所需的循环次数。改进后对n个数据进行排序,最多只需进行[n/2]趟循环即可。具体实现如下:

void SelectSort(int r[],int n) {
	int i ,j , min ,max, tmp;
	for (i=1 ;i <= n/2;i++) {  
		// 做不超过n/2趟选择排序 
		min = i; max = i ; //分别记录最大和最小关键字记录位置
		for (j= i+1; j<= n-i; j++) {
			if (r[j] > r[max]) { 
				max = j ; continue ; 
			}  
			if (r[j]< r[min]) { 
				min = j ; 
			}   
	  }  
	  //该交换操作还可分情况讨论以提高效率
	  tmp = r[i-1]; r[i-1] = r[min]; r[min] = tmp;
	  tmp = r[n-i]; r[n-i] = r[max]; r[max] = tmp; 

	} 
}

4. 选择排序—堆排序(Heap Sort)

堆排序是一种树形选择排序,是对直接选择排序的有效改进。

基本思想:

堆的定义如下:具有n个元素的序列(k1,k2,...,kn),当且仅当满足


时称之为堆。由堆的定义可以看出,堆顶元素(即第一个元素)必为最小项(小顶堆)。
若以一维数组存储一个堆,则堆对应一棵完全二叉树,且所有非叶结点的值均不大于(或不小于)其子女的值,根结点(堆顶元素)的值是最小(或最大)的。如:

(a)大顶堆序列:(96, 83,27,38,11,09)

  (b)  小顶堆序列:(12,36,24,85,47,30,53,91)

初始时把要排序的n个数的序列看作是一棵顺序存储的二叉树(一维数组存储二叉树),调整它们的存储序,使之成为一个堆,将堆顶元素输出,得到n 个元素中最小(或最大)的元素,这时堆的根节点的数最小(或者最大)。然后对前面(n-1)个元素重新调整使之成为堆,输出堆顶元素,得到n 个元素中次小(或次大)的元素。依此类推,直到只有两个节点的堆,并对它们作交换,最后得到有n个节点的有序序列。称这个过程为堆排序

因此,实现堆排序需解决两个问题:
1. 如何将n 个待排序的数建成堆;
2. 输出堆顶元素后,怎样调整剩余n-1 个元素,使其成为一个新堆。


首先讨论第二个问题:输出堆顶元素后,对剩余n-1元素重新建成堆的调整过程。
调整小顶堆的方法:

1)设有m 个元素的堆,输出堆顶元素后,剩下m-1 个元素。将堆底元素送入堆顶((最后一个元素与堆顶进行交换),堆被破坏,其原因仅是根结点不满足堆的性质。

2)将根结点与左、右子树中较小元素的进行交换。

3)若与左子树交换:如果左子树堆被破坏,即左子树的根结点不满足堆的性质,则重复方法 (2).

4)若与右子树交换,如果右子树堆被破坏,即右子树的根结点不满足堆的性质。则重复方法 (2)

5)继续对不满足堆性质的子树进行上述交换操作,直到叶子结点,堆被建成。

称这个自根结点到叶子结点的调整过程为筛选。如图:



再讨论对n 个元素初始建堆的过程。
建堆方法:对初始序列建堆的过程,就是一个反复进行筛选的过程。

1)n 个结点的完全二叉树,则最后一个结点是第个结点的子树。

2)筛选从第个结点为根的子树开始,该子树成为堆。

3)之后向前依次对各结点为根的子树进行筛选,使之成为堆,直到根结点。

如图建堆初始过程:无序序列:(49,38,65,97,76,13,27,49)
                              


                              

算法的实现:

从算法描述来看,堆排序需要两个过程,一是建立堆,二是堆顶与堆的最后一个元素交换位置。所以堆排序有两个函数组成。一是建堆的渗透函数,二是反复调用渗透函数实现排序的函数。


package com;

/*
 * Java实现快速排序算法
 * 由大到小排序
 * author:wyr
 * 2016-7-14
 *两个步骤:1,建堆  2,对顶与堆的最后一个元素交换位置
 */
public class HeapSort {

	public static void main(String[] args) {
		int a[] = {3,1,5,7,2,4,9,6,10,8};  
		HeapSort  obj=new HeapSort();
		System.out.println("初始值:");
		obj.print(a);
		
		for(int i=0;i<a.length;i++){
			obj.createLittleHeap(a,a.length-1-i);//创建堆,创建的是小顶堆。每次循环完,二叉树的根节点都是最小值,所以与此时的未排好部分最后一个值交换位置
			obj.swap(a, 0, a.length - 1 - i);//与最后一个值交换位置,最小值找到了位置
            obj.print(a);
            System.out.println();
			
		}
		
		System.out.println("\n排序后:");
		obj.print(a);

	}
	/*
	 * 创建小顶堆:双亲节点小于子节点的值。从叶子节点开始,直到根节点。这样建立的堆定位最小值
	 */
	private void createLittleHeap(int[] data, int last) {
		 for (int i = (last- 1) / 2; i >= 0; i--) {  //找到最后一个叶子节点的双亲节点
	            // 保存当前正在判断的节点  
	            int parent = i;  
	            // 若当前节点的左子节点存在,即子节点存在
	            while (2 * parent + 1 <= last) {  
	                // biggerIndex总是记录较大节点的值,先赋值为当前判断节点的左子节点  
	                int bigger = 2 * parent + 1;//bigger指向左子节点  
	                if (bigger < last) { //说明存在右子节点
	                  
	                    if (data[bigger] > data[bigger+ 1]) { //右子节点>左子节点时
	                     
	                        bigger=bigger+1;  
	                    }  
	                }  
	                if (data[parent] > data[bigger]) {  //若双亲节点值大于子节点中最大的
	                    // 若当前节点值比子节点最大值小,则交换2者得值,交换后将biggerIndex值赋值给k  
	                    swap(data, parent, bigger);  
	                    parent = bigger;  
	                } else {  
	                    break;  
	                }  
	            }  
	        }  
	}
	public void print(int a[]){
		for(int i=0;i<a.length;i++){
			System.out.print(a[i]+" ");
		}
	}
	 public  void swap(int[] data, int i, int j) {  
	        if (i == j) {  
	            return;  
	        }  
	        data[i] = data[i] + data[j];  
	        data[j] = data[i] - data[j];  
	        data[i] = data[i] - data[j];  
	    }  
}



猜你喜欢

转载自blog.csdn.net/zcl183/article/details/79957384