面试准备之常见排序算法的总结!

1.插入排序

 

1.1直接插入排序

 

    这是一种最简单,最容易理解的排序方式,其排序思路如下:假设待排序数组为R[0,1,2,i...n-1],首先将R[0]看作一个有序的子序列(尽管它只有一个数),从R[1]至R[n-1]开始,逐一向该子序列进行插入操作(由小到大排序),那么左半部分的有序子序列将不断增加,而右半部分的待排序列将不断减少,当有序子序列的长度等于原序列时, 排序结束。
   时间复杂度:O(n^2)
   稳定性:稳定

 

    然而,如何找到待插入元素的位置是插入排序的关键,直接插入排序用的就是最“暴力”的逐一查找法:

    暴力算法1:将R[i]与有序子序列所有的元素比较(不移动,只寻找),找到待插入的位置j后,将R[j,i-1]一次性移动到R[j+1,i],腾出R[j]的位置让R[i]来插入,其中遍历了一次,移动了一次,很是暴力。

    暴力算法2:在算法1的基础上,每次寻找时,若R[i]小于子序列的元素,就与其交换,这样在遍历的同时就完成了移动的动作,优于算法1。

 

    算法1的实现如下,

 

//算法1
		public void insertSort1(TestSort[] src){
			TestSort[] r=this.copy(src);
			int len=r.length;
			for(int i=1;i<=len-1;i++){//从第二个数开始插入
				int location=i;//寻找待插入位置
				int tmp=r[i].value;//暂存待插入元素
				for(int j=i-1;j>=0;j--){
					//j代表有序子序列的最大索引
					if(!this.compare(r[i],r[j])){
						//若小于,则继续寻找位置
						location=j;
					}
				}
				
				if(location!=i){
				//将R[location...i-1]移动到R[location+1,i],腾出location处的位置
					for(int k=i;k>location;k--){
						r[k].value=r[k-1].value;
					}
				}
				r[location].value=tmp;//填充待插入元素
			}
			//输出
			this.print(r);
		}


算法2的实现如下:

 

//算法2,优于算法1
		public void insertSort2(TestSort[] src){
			TestSort[] r=this.copy(src);
			int len=r.length;
			for(int i=1;i<=len-1;i++){//从第二个数开始插入
				int tmp=r[i].value;//暂存待排序元素
				int j=i-1;
				while((j>=0)&&(tmp<r[j].value)){
					
					r[j+1].value=r[j].value;
					j--;
					
				}
				r[j+1].value=tmp;//填充待插入元素
			}
			//输出
			this.print(r);
		}


而直接插入排序一般用算法2就行了。

 

1.2折半插入排序

 

    尽管直接插入排序的算法2优于算法1,但它们都比较暴力,因为若子序列的长度为n,则复杂度就为O(n)。假若有一个数我们第一眼就知道它应该插入到最前面,而有序序列又很长,那么这就是最坏的情况(得从后面一个一个找)。为了能快速找到待插入位置,就引入了能快速寻找的折半查找。

    折半插入排序的原理就是:   在直接插入排序排序的基础上,每次寻找插入位置时将有序子序列分解为高半区high, 和低半区low,mid为(low+high)/2,当待排元素key小于(大于)mid时,则它应该插入低半区(高半区),依次循环。

   时间复杂度:O(n^2)
   稳定性:稳定

算法实现:

public void halfInsert(TestSort[] src){
			TestSort[] r=this.copy(src);
			int len=r.length;
			int low,high,tmp;
			int mid;
			for(int i=0;i<len;i++){
				low=0;//low指向子序列的起始位置
				high=i-1;//high指向末端		
				tmp=r[i].value;//待插入元素
				//寻找位置
				while(low<=high){
					mid=(low+high)/2;
					if(r[i].value<r[mid].value)//小于mid
						high=mid-1;
					else
						low=mid+1;
				}
				//high+1则为待插入位置
				//将r[high+1...i-1]移动到r[high+2...i],腾出high+1的位置
				for(int j=i;j>high+1;j--){
					r[j].value=r[j-1].value;
				}
				r[high+1].value=tmp;
			}
			//输出
			this.print(r);
		}


1.3希尔排序

 

    希尔排序是一种分组插入的排序方式,它首先将待排序R[]序列按照d1=R.length/2的增量进行分组,然后组内的元素进行插入排序,然后以d2=d1/2的增量重复上述操作,直至dn=1(即所有元素都在同一组内)则完成排序。
   时间复杂度:O(logn*logn*n)=O(n*log2n)
   稳定性:不稳定

算法实现:

public void shellSort(TestSort[] src){
			TestSort[] r=this.copy(src);
			int len=r.length;
			int d=len/2;
			while(d>=1){
				//以d为分组的序列进行直接插入排序,
				for(int i=d;i<len;i=i+d){
					int tmp=r[i].value;//暂存待排序元素
					int j=i-d;//有序子序列的最大索引
					while(j>=0&&tmp<r[j].value){
							r[j+d].value=r[j].value;
							j=j-d;
					}
					//此时的j+d为待插入位置
					r[j+d].value=tmp;
				}
				d=d/2;//增量递减
			}
			//输出
			this.print(r);
		}

2.交换排序

 

2.1冒泡排序

 

    冒泡排序是从第一个元素起,比较相邻的两个元素,按由小到大排序,若前一个大于后一个元素,则交换彼此。如此重复n-1趟,每趟都会将该趟最大的元素放置趟尾,好像“冒泡”一样。
   时间复杂度:O(n^2)
   稳定性:稳定

算法实现:

public void bubbleSort(TestSort[] src){
			TestSort[] r=this.copy(src);
			int len=r.length;
			for(int i=0;i<len;i++){//len-1趟
				for(int j=0;j<len-i-1;j++){//每趟交换的次数
					if(r[j].value>r[j+1].value){
						this.swap(r[j],r[j+1]);
					}
				}		
			}
			//输出
			this.print(r);
		}


2.2快速排序

 

    快速排序是在冒泡排序的基础上改进的:首先选取R[0...n]序列中的R[0]做为关键字,low和high分别指向序列的开始和结束(低半区和高半区),将小于(大于)R[0]的元素从低半区(高半区)交换至高半区(低半区)而中间则插入R[0],递归对两边子序列也进行上述操作,直至子序列元素的个数为1。
   时间复杂度:将原始序列无限按二分法分解则复杂度为logn,遍历时low--->high则为n,所以O(logn*n)=O(nlogn)
   稳定性:不稳定

算法实现:

	public void quickSort(TestSort[] src,int lowIndex,int highIndex){
			
				if(lowIndex<highIndex){//保证递归时至少有2个元素
					TestSort[] r=src;
					int low=lowIndex;
					int high=highIndex;
				  int location;
					int key=r[low].value;//关键字
						while(low<high){
							while(low<high&&r[high].value>=key){	
								high--;
							}
							if(low<high){
								this.swap(r[high],r[low]);
							}
							while(low<high&&r[low].value<=key){		
								low++;
							}
							if(low<high){
								this.swap(r[low],r[high]);
							}
						}		
					//待插入位置location=low=high
					location=low;
					r[location].value=key;
					quickSort(r,lowIndex,location-1);//低区递归
					quickSort(r,location+1,highIndex);//高区递归		
				}
				
		}


3.选择排序

 

3.1简单选择排序

 

    设待排序列R[0...i...n-1],待排元素为R[i],有序子序列为R[0...i-1],无序子序列为R[i...n-1],则在 R[i...n-1]中寻找最小的元素,并与R[i]交换(相当于补在有序子序列的后边),直到有序子序列的长度等于原长为止。
   时间复杂度:O(n^2)
   稳定性:不稳定

算法实现:

public void simpleChoose(TestSort[] src){
			TestSort[] r=this.copy(src);
			int len=r.length;
			for(int i=0;i<len;i++){
				int location=i;
				int min=r[location].value;
				for(int j=i+1;j<len;j++){
					if(r[j].value<min){
						location=j;
						min=r[j].value;
					
					}
				}
				if(location!=i){
					//找到了,在location处
					this.swap(r[location],r[i]);
				}
				
			}
			//输出
			this.print(r);
		}

 


所有排序算法实现:

 

import java.util.ArrayList;
public class TestSort{
		public static final int SYSMBOL=0XFF;//占位符号
		public int value;//值
		public int index;//索引

		/*
			原本想利用对象最为带排序元素存些其他信息,最后发现木有必要。。
		*/
		public TestSort(int value,int index){
			
			this.value=value;
			this.index=index;
		}
		public TestSort(){}
		
		//交换
		public void swap(TestSort a,TestSort b){
			
			TestSort tmp=new TestSort();
			tmp.value=a.value;
			//tmp.index=a.index;
			
			a.value=b.value;
			//a.index=b.index;
			
			b.value=tmp.value;
			//b.index=tmp.index;
			
		
		}
		
		//比大小
		public Boolean compare(TestSort a,TestSort b){
			if(a.value>b.value)
				return true;
				return false;
		}
		
		//输出
		public void print(TestSort[] r){
		
			System.out.printf("%s","索引:");
			for(TestSort e : r){
				
				if(e.value!=TestSort.SYSMBOL){
					System.out.printf("%s ",e.index<10?"0"+e.index:e.index);
				}
			}
			System.out.println("\n");
			System.out.printf("%s","数组:");
			for(TestSort e : r){
				if(e.value!=TestSort.SYSMBOL){
					System.out.printf("%s ",e.value<10?"0"+e.value:e.value);
				}
			}
			System.out.println("\n");
		}
		
		//复制元素,由于数组为引用类型
		public TestSort[] copy(TestSort[] src){
			  ArrayList<TestSort> list=new ArrayList<TestSort>();
				for (TestSort e : src) {
				 	list.add(new TestSort(e.value,e.index));
				}
				TestSort[] r=new TestSort[list.size()];
				return list.toArray(r);
		}
		/********************************1.插入排序***************************************************/
		/*
		---------------------------------1.1直接插入排序:---------------------------------------------------
			这是一种最简单,最容易理解的排序方式,其排序思路如下,
		假设待排序数组为R[0,1,2,i...n],首先将R[0]看作一个有序的
		子序列(尽管它只有一个数),从R[1]至R[n]开始,逐一向该子序列
		进行插入操作(由小到大),那么左半部分的子序列将不断增加,而
		有半部分的待排序列将不断减少,当有序子序列的长度等于原序列时,
		排序结束。
			时间复杂度:O(n^2)
			稳定性:稳定
		*/
		
		//算法1
		public void insertSort1(TestSort[] src){
			TestSort[] r=this.copy(src);
			int len=r.length;
			for(int i=1;i<=len-1;i++){//从第二个数开始插入
				int location=i;//寻找待插入位置
				int tmp=r[i].value;//暂存待插入元素
				for(int j=i-1;j>=0;j--){
					//j代表有序子序列的最大索引
					if(!this.compare(r[i],r[j])){
						//若小于,则继续寻找位置
						location=j;
					}
				}
				
				if(location!=i){
				//将R[location...i-1]移动到R[location+1,i],腾出location处的位置
					for(int k=i;k>location;k--){
						r[k].value=r[k-1].value;
					}
				}
				r[location].value=tmp;//填充待插入元素
			}
			//输出
			this.print(r);
		}
		
		
		//算法2,优于算法1
		public void insertSort2(TestSort[] src){
			TestSort[] r=this.copy(src);
			int len=r.length;
			for(int i=1;i<=len-1;i++){//从第二个数开始插入
				int tmp=r[i].value;//暂存待排序元素
				int j=i-1;
				while((j>=0)&&(tmp<r[j].value)){
					
					r[j+1].value=r[j].value;
					j--;
					
				}
				r[j+1].value=tmp;//填充待插入元素
			}
			//输出
			this.print(r);
		}
		
		
		/*
		---------------------------------1.2折半插入排序:---------------------------------------------------
					在简单插入排序中,为了插入到有序子序列中的合适位置,每次不得不遍历所有的子序列,这样其实复杂度很大。
			假若有一个数我们第一眼就知道它应该插入到最前面,而有序序列又很长,那么这就是最坏的情况(得从后面一个一个找)。
			为了快速找到合适的插入位置,可以利用折半查找:在简单排序的基础上,每次寻找插入位置时将有序子序列分解为高半区high,
			和低半区low,mid为(low+high)/2,当待排元素key小于(大于)mid时,则它应该插入低半区(高半区),依次循环,这样复杂度
			一下子就降为logn了,于是,折半插入排序的性能为,
			时间复杂度:O(nlogn)
			稳定性:稳定
		*/
		public void halfInsert(TestSort[] src){
			TestSort[] r=this.copy(src);
			int len=r.length;
			int low,high,tmp;
			int mid;
			for(int i=0;i<len;i++){
				low=0;//low指向子序列的起始位置
				high=i-1;//high指向末端		
				tmp=r[i].value;//待插入元素
				//寻找位置
				while(low<=high){
					mid=(low+high)/2;
					if(r[i].value<r[mid].value)//小于mid
						high=mid-1;
					else
						low=mid+1;
				}
				//high+1则为待插入位置
				//将r[high+1...i-1]移动到r[high+2...i],腾出high+1的位置
				for(int j=i;j>high+1;j--){
					r[j].value=r[j-1].value;
				}
				r[high+1].value=tmp;
			}
			//输出
			this.print(r);
		}
		
		/*
		---------------------------------1.3希尔排序:---------------------------------------------------
					希尔排序是一种分组插入的排序方式,它首先将待排序R[]序列按照d1=R.length/2的增量进行分组,然后
			组内的元素进行插入排序,然后以d2=d1/2的增量重复上述操作,直至dn=1(即所有元素都在同一组内)则
			完成排序。
			时间复杂度:O(logn*logn*n)=O(n*log2n)
			稳定性:不稳定
		*/
		public void shellSort(TestSort[] src){
			TestSort[] r=this.copy(src);
			int len=r.length;
			int d=len/2;
			while(d>=1){
				//以d为分组的序列进行直接插入排序,
				for(int i=d;i<len;i=i+d){
					int tmp=r[i].value;//暂存待排序元素
					int j=i-d;//有序子序列的最大索引
					while(j>=0&&tmp<r[j].value){
							r[j+d].value=r[j].value;
							j=j-d;
					}
					//此时的j+d为待插入位置
					r[j+d].value=tmp;
				}
				d=d/2;//增量递减
			}
			//输出
			this.print(r);
		}
		/********************************2.交换排序***************************************************/
		/*
		---------------------------------2.1冒泡排序:---------------------------------------------------
					冒泡排序是从第一元素起,比较相邻的两个元素,按由小到大排序,若前一个大于后一个元素,则交换彼此。
			如此重复n-1趟,每趟都会将该趟最大的元素放置趟尾,好像“冒泡”一样。
			时间复杂度:O(n^2)
			稳定性:稳定
		*/
		public void bubbleSort(TestSort[] src){
			TestSort[] r=this.copy(src);
			int len=r.length;
			for(int i=0;i<len;i++){//len-1趟
				for(int j=0;j<len-i-1;j++){//每趟交换的次数
					if(r[j].value>r[j+1].value){
						this.swap(r[j],r[j+1]);
					}
				}		
			}
			//输出
			this.print(r);
		}
		
		/*
		---------------------------------2.2快速排序:---------------------------------------------------
					快速排序是在冒泡排序的基础上改进的:首先选取R[0...n]序列中的R[0]做为关键字,low和high分别指向
			序列的开始和结束(低半区和高半区),将小于(大于)R[0]的元素从低半区(高半区)交换至高半区(低半区)
			而中间则插入R[0],递归对两边子序列也进行上述操作,直至子序列的个数为1
			时间复杂度:将原始序列无限按二分法分解则复杂度为logn,遍历时low--->high则为n
									所以O(logn*n)=O(nlogn)
			稳定性:不稳定
		*/
		public void quickSort(TestSort[] src,int lowIndex,int highIndex){
			
				if(lowIndex<highIndex){//保证递归时至少有2个元素
					TestSort[] r=src;
					int low=lowIndex;
					int high=highIndex;
				  int location;
					int key=r[low].value;//关键字
						while(low<high){
							while(low<high&&r[high].value>=key){	
								high--;
							}
							if(low<high){
								this.swap(r[high],r[low]);
							}
							while(low<high&&r[low].value<=key){		
								low++;
							}
							if(low<high){
								this.swap(r[low],r[high]);
							}
						}		
					//待插入位置location=low=high
					location=low;
					r[location].value=key;
					quickSort(r,lowIndex,location-1);//低区递归
					quickSort(r,location+1,highIndex);//高区递归		
				}
				
		}
		
		/********************************3.选择排序***************************************************/
		/*
		---------------------------------3.1简单选择排序:---------------------------------------------------
					设待排序列R[0...i...n-1],待排元素为R[i],有序子序列为R[0...i-1],无序子序列为R[i...n-1],则在
			R[i...n-1]中寻找最小的元素,并与R[i]交换(相当于补在有序子序列的后边),直到有序子序列的长度等于原长为止。
			时间复杂度:O(n^2)
			稳定性:不稳定
		*/
		public void simpleChoose(TestSort[] src){
			TestSort[] r=this.copy(src);
			int len=r.length;
			for(int i=0;i<len;i++){
				int location=i;
				int min=r[location].value;
				for(int j=i+1;j<len;j++){
					if(r[j].value<min){
						location=j;
						min=r[j].value;
					
					}
				}
				if(location!=i){
					//找到了,在location处
					this.swap(r[location],r[i]);
				}
				
			}
			//输出
			this.print(r);
		}
		
		public static void main(String[] args){
			TestSort obj=new TestSort();
			TestSort[] r=new TestSort[]{
				new TestSort(10,0),
				new TestSort(1,1),
				new TestSort(9,2),
				new TestSort(2,3),
				new TestSort(8,4),
				new TestSort(3,5),
				new TestSort(7,6),
				};
				System.out.println("原始数组:");
				obj.print(r);
				
				System.out.println("直接插入算法1:");
				obj.insertSort1(r);
				
				System.out.println("直接插入算法2:");
				obj.insertSort2(r);
				
				System.out.println("折半插入算法:");
				obj.halfInsert(r);
				
				System.out.println("希尔排序算法:");
				obj.shellSort(r);
				
				System.out.println("冒泡算法:");
				obj.bubbleSort(r);
				
				System.out.println("快速排序:");
				obj.quickSort(r,0,r.length-1);
				obj.print(r);
				
				System.out.println("简单选择数组:");
				obj.simpleChoose(r);
			  
				
				
		}
	}


结果:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

   

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值