图解、手撕十大排序算法[思路清晰版]

常见的排序算法有冒泡排序、选择排序、插入排序、希尔排序、归并排序、快速排序、堆排序、桶排序、计数排序、基数排序等。其中最熟悉的是冒泡排序,记得笔者第一次接触冒泡排序还是2008年参加学校的ACM比赛之前学习的,其他排序基本都是在面试、考研等考试中遇到,最晚接触的是计数排序,是在2017年读研后上的《算法分析与设计》门课中接触。其中最熟悉的是冒泡排序,因为接触时间长,也最简单。其他的排序算法基本思想也了解一点,有些细节可能会有遗漏,最近在程序界流行一个词–“手撕”,下面先讲一下有关排序算法的性质,然后手撕这几种常见的排序算法。

专业术语说明
稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面;
不稳定:如果a原本在b的前面,而a=b,排序之后a可能会出现在b的后面;
内排序:所有排序操作都在内存中完成;
外排序:由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行;
时间复杂度: 一个算法执行所耗费的时间。
空间复杂度:运行完一个程序所需内存的大小。

笔者发现平常最容易考察的就是排序算法对比,先将排序算法对比列表如下:

排序算法 平均时间复杂度 最好情况下时间复杂度 最坏情况下时间复杂度 空间复杂度 排序方式 稳定性
冒泡排序 O(n2) O(n) O(n2) O(1) in-place 稳定
选择排序 O(n2) O(n2) O(n2) O(1) in-place 不稳定(每趟选择最小的和待插入位置互换)
插入排序 O(n2) O(n) O(n2) O(1) in-place 稳定
希尔排序 O(nlogn) O(nlogn) O(nlogn) O(1) in-place 不稳定
归并排序 O(nlogn) O(nlogn) O(nlogn) O(1) out-place 稳定
快速排序 O(nlogn) O(nlogn) O(n2) O(logn) in-place 不稳定
堆排序 O(nlogn) O(nlogn) O(nlogn) O(1) in-place 不稳定
计数排序 O(n+k) O(n+k) O(n+k) O(k) out-place 稳定
桶排序 O(n+k) O(n+k) O(n2) O(n+k) out-place 稳定
基数排序 O(n*k) O(n*k) O(n*k) O(n+k) out-place 稳定

n: 数据规模 k:“桶”的个数 In-place: 占用常数内存,不占用额外内存 Out-place: 占用额外内存

图解10种排序算法:
1.冒泡排序
在这里插入图片描述
2.选择排序
在这里插入图片描述
3.插入排序
在这里插入图片描述

4.希尔排序
在这里插入图片描述

5.归并排序
在这里插入图片描述
6.快速排序
在这里插入图片描述
7.堆排序
在这里插入图片描述
8.桶排序
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述9.计数排序
在这里插入图片描述
10.基数排序
在这里插入图片描述

package rank;

import java.util.ArrayList;
import java.util.Collections;

public class CehRank {
	
	//1.冒泡排序<时间复杂度O(n*n),空间复杂读O(n),稳定排序>
	public static void bubbleSort(int[] data) {
		int len = data.length;
		int lastSwap = len;//记录最后一次交换的位置
		for(int i = 0; i<len; i++) {
			boolean hasSwap = false;
			int sortBorder = lastSwap ;// 无序数列的边界,每次比较只需要比到这里为止
			for(int j=1; j<sortBorder; j++) {
				if(data[j]<data[j-1]) {
					int tmp = data[j];
					data[j] = data[j-1];
					data[j-1] = tmp;
					hasSwap = true;// 有元素交换
					lastSwap = j;// 最后一次交换元素的位置
				}
			}
			if(!hasSwap) {
				break;//如果在这一趟内排序没有发生交换,说明数组已经有序,直接退出
			}
		}
	}
	
	//2.选择排序<时间复杂度O(n*n),空间复杂度O(n),不稳定排序>
	public static void selectSort(int[] data) {
		int len = data.length;
		for(int i=0; i<len; i++) {
			int min=i;
			for(int j=i+1; j<len; j++) {
				if(data[j]<data[min]) {
					min = j;
				}
			}
			int tmp = data[min];
			data[min] = data[i];
			data[i] = tmp;
		}
	}
	//3.插入排序<时间复杂度O(n*n),空间复杂度O(1),稳定排序>
	public static void insertSort(int[] data) {
		int len = data.length;
		for(int i=1; i<len; i++) {//需要待插入的数
			int value = data[i];//待插的数据
			int j =0;//待插入的位置
			for( j=i-1; j>=0; j--) {
				if(value<data[j]) {
					data[j+1] = data[j];
				} else {
					break;
				}
			}
			data[j+1] = value;
		}
	}
	
	//4、希尔排序:插入排序改进版<时间复杂度O(n*log(n)),空间复杂度O(1),不稳定排序>
	public static void hillSort(int[] data) {
		int len = data.length;
		int gap = 1;
		while(gap<len/3) {
			gap *= 3+1;
		}
		while(gap>0) {
			for(int i=gap; i<len; i++) {
				int currentIndex = i;
				int pIndex = currentIndex - gap;
				while(pIndex>=0) {
					if( data[currentIndex]<data[pIndex] ) {
						int tmp = data[currentIndex] ;
						data[currentIndex] = data[pIndex];
						data[pIndex] = tmp;
					}
					currentIndex = pIndex;
					pIndex -= gap;
				}
			}
			gap = (int)Math.floor(gap/3);
		}
	}
	
	//5.归并排序<时间复杂度O(n*log(n)),空间复杂度O(n),稳定排序>
	public static void mergeSort(int[] data) {
		divideMerge(data, 0, data.length-1);
	}
	
	//归并排序:分解
	public static void divideMerge(int[] data, int start, int end) {
		if(end<=start) {return;}
		int mid = (start+end)/2;
		divideMerge(data, start, mid);
		divideMerge(data, mid+1, end);		
		
		merge(data, start, mid, end);
	}
	//归并排序:合并
	private static void merge(int[] data,  int start, int middle, int end) {
		int len = data.length;
		int[] temp = new int[len];
		//复制要合并的数据
		for(int i=start; i<= end; i++) {temp[i] =data[i];}
		
		int left = start, right = middle+1;
		for(int i= start; i<=end; i++) {
			if(left>middle) {
				//如果左边的首位下标大于中部下标,证明左边的数据已经排完了。
				data[i] = temp[right++];
			} else if(right>end) {
				//如果右边的首位下标大于了数组长度,证明右边的数据已经排完了。
				data[i] = temp[left++];
			} else if(temp[right]<temp[left]) {
				data[i] = temp[right++];//将右边的首位排入,然后右边的下标指针+1。
			}else {
				data[i]= temp[left++];//将右边的首位排入,然后右边的下标指针+1。
			}
		}
	}
	
	
	//6.快速排序:此处用递归实现,还可以考虑用栈操作替代递归实现<时间复杂度O(n*log(n)),空间复杂度O(log(n)),不稳定排序>
	public static void quickSort(int start, int end, int[] data) {
		if(start>= end) return;
		int p = partion(start,end,data);
		quickSort(start, p-1, data);
		quickSort(p+1,end,data);
	}
	
	//快速排序:双边扫描
	public static int partion(int start, int end, int[] data) {
		int left = start, right = end, p = data[start] ;
		while(left!=right) {
			while(data[right]>p&&left<right) {
				right--;
			}
			while(data[left]<=p&&left<right) {//”=“是为了保证排序算法的稳定性
				left++;
			}
			if(left<right) {
				int tmp = data[right];
				data[right] = data[left];
				data[left] = tmp;
			}
		}
		data[start] = data[left];
		data[left] = p;
		return left;
	}
	
	//7.堆排序<时间复杂度O(n*log(n)),空间复杂度O(1),不稳定排序>
	public static void heapSort(int[] data) {
		int len = data.length;
		
		//构建堆
		buildHeap(data, len);
		for(int i=len-1; i>0; i--) {
			int tmp = data[0];
			data[0] = data[i];
			data[i] = tmp;
			len--;
			sink(data, 0, len);
		}
	}

	//建堆
	private static void buildHeap(int[] data, int len) {
		for(int i=len/2; i>=0; i--) {
			sink(data, i, len);
		}
	}
	
	//调整:保证堆为大顶堆
	private static void sink(int[] data, int index, int len) {
	    int leftChild = 2 * index + 1;//左子节点下标
	    int rightChild = 2 * index + 2;//右子节点下标
	    int current = index;//要调整的节点下标
	    
	    //下沉左边
	    if (leftChild < len && data[leftChild] > data[current]) {
	        current = leftChild;
	    }

	    //下沉右边
	    if (rightChild < len && data[rightChild] > data[current]) {
	        current = rightChild;
	    }
	    
	    if(current!=index) {
	    	int tmp = data[index];
	    	data[index] = data[current];
	    	data[current] = tmp;
	    	//可能换下来的父节点比子节点小,所以继续下沉
	    	sink(data, current, len);
	    }
	}

	///8.计数排序<时间复杂度O(n+k),空间复杂度O(n+k),稳定排序>
	public static void countSort(int[] data) {
		int len = data.length;
		int max=0, min=0;
		for(int i=0; i<len; i++) {
			if(max<data[i]) {max=data[i];}
			if(min>data[i]) {min=data[i];}
		}
		int countLen = max-min+1;
		int[] counts = new int[countLen];
		for(int i=0; i<len; i++) {
			counts[ data[i]-min ]++;
		}
		int index = 0;
		for(int i=0; i<countLen; i++) {
			while(counts[i]>0) {
				data[index++] = i+ min;
				counts[i]--;
			}
		}
	}
	//9.桶排序<时间复杂度O(n+k),空间复杂度O(n+k),稳定排序>
	public static void bucketSort(int[] data) {
		int len = data.length;
		int max=data[0],min=data[0];
		for(int i=0; i<len; i++) {
			if(max<data[i]) {max=data[i];}
			if(min>data[i]) {min=data[i];}
		}
		int gap = max - min;
		ArrayList<ArrayList<Integer>> buckets = new ArrayList<ArrayList<Integer>>();
		for(int i=0; i<len; i++) {
			buckets.add(new ArrayList<Integer>());
		}
		
		//每个桶大小,因为不一定会除尽,余下的全部放到最后一个桶中
		int bucketSize = (int) Math.ceil((float)gap/ len);
		if(bucketSize<1) bucketSize = 1;
		
		//数据入桶
		for(int i=0; i<len; i++) {
			int index = (data[i]-min)/bucketSize ;
			buckets.get(index).add(data[i]);
		}
		int index =0;
		for(int i=0; i<len; i++) {
			ArrayList<Integer> bucket = buckets.get(i);
			if(bucket!=null&&bucket.size()!=0) {
				Collections.sort(buckets.get(i));
				for(int value:bucket) {
					data[index++] = value;
				}
			}
		}
	}
	
	//10.基数排序<时间复杂度O(n+k),空间复杂度O(n+k),稳定排序>
	public static void radixSort(int[] data) {
		int len = data.length;
		int max=data[0];
		for(int i=0; i<len; i++) {
			if(max<data[i]) {max=data[i];}
		}
		ArrayList<ArrayList<Integer>> buckets = new ArrayList<ArrayList<Integer>>();
		for(int i=0; i<10; i++) {
			buckets.add(new ArrayList<Integer>());
		}
		
		int radix = 1;//底数,找到待排数的相应位
		while(true) {
			if(max<radix) {break;}//当最大的数比底数小,表示所有的位已经排完
			for(int i=0; i<len; i++) {
				int index = (data[i]/radix)%10;
				buckets.get(index).add(data[i]);
			}
			//将桶中的数写回
			int index = 0;
			for(int i=0; i<10; i++) {
				for(int value:buckets.get(i)) {
					data[index++] = value;
				}
				buckets.get(i).clear();
			}
			radix *= 10;
		}
	}
	
	public static void print(int[] data) {
		for(int i=0; i<data.length; i++) {
			System.out.print(data[i]+"\t");
		}
		System.out.println();
	}
	
	public static void main(String[] args) {
		int data[] = {912, 132, 182, 888, 218, 322, 511, 522, 533,  692,2,  22, 782,398, 999,  439 };
		//int data[] = {6,4,2,4,1,5};
		print(data);
		//bubbleSort(data);
		//selectSort(data);
		//insertSort(data);
		//hillSort(data);
		//mergeSort(data);
		//quickSort(0, data.length-1, data);
		heapSort(data);
		//countSort(data);
		//bucketSort(data);
		//radixSort(data);
		print(data);
	}
}
发布了80 篇原创文章 · 获赞 133 · 访问量 20万+

猜你喜欢

转载自blog.csdn.net/chekongfu/article/details/98250905