十大排序算法简记

版权声明:学海无涯,来点干货 https://blog.csdn.net/Jackponwong/article/details/89677831

在这里插入图片描述

  1. 选择排序( Selection )
    选择排序是最简单的,也是最没有用的,因为它的时间复杂度是 O( n^2 ) ,而且还不稳定。
    思路:
    从左到右扫描过去,找出最小的数的下标,跟这一趟的最开始数的下标index 交互位置,后面重复第一趟过程。
  2. 冒泡排序( Bubble )
    顾名思义,类似于冒泡的过程,它的时间复杂度是 O( n^2 ) ,稳定
    思路:
    从左到右扫描到 index,比较相邻两个数的大小,如果左边大于右边,则交换两值(大数就冒到右边了),index - - ;重复上述过程。
  3. 插入排序( Insertion )
    适用于基本有序的数组,它的时间复杂度是 O( n^2 ) ,稳定。
    思路:
    类似于扑克牌的插入排序;
static void insertion(int[] a){
		for (int i = 1; i < a.length; i++) {
			for (int j = i; j > 0; j--) {
				if (a[j] < a[j-1]) {
					swap(a, j, j-1);
				}
			}
		}
	}
  1. 快速排序( Quick )
    最常用高效的排序算法,它的时间复杂度是 O( n*log_2 n ) ,不稳定。
    思路:
    一定要先从右到左,找比轴小的值的下标high;再从左到右,找比轴大的值的下标low ;判断是否 high <= low ;不是的话就交换high和low ;一个回合结束后,交换high和key,然后递归左右半区;
	 static void quick(int[] a, int left, int right){
		 if (right<=left) {
			return;
		}
		 int i=left,j=right;
		 int key = a[left];//默认第一个为轴就行
		 while(i<j){
			 //从右到左找小于key的值
			 while(i<j && a[j]>=key){
				 j--;
			 }
			//从左到右找大于key的值
			 while(i<j && a[i]<=key){
				 i++;
			 }
			 if(i<j){
				 swap(a, i, j);
			 }else {
				break;
			}
		 }
		 swap(a, j, left);
		 //为什么右边一定要先行的原因
		 quick(a, left, j-1);
		 quick(a, j+1, right);
	 }
  1. 归并排序( Merge )
    Java对象排序默认使用的算法,它的时间复杂度是 O( n*log_2 n ) ,稳定。
    思路:
    将一个数组分成两半基本有序的数组(利用递归实现),再将两半有序的数组合并成有序的数组;怎么将两半基本有序的数组合并呢?
    定义两个指针分别指向两个数组的开始位置,比较大小,然后复制到新建的数组,最后将没移动完的数组全部复制到新的数组。
/**
*	通过递归实现一个数组分成两半基本有序
*/
static void MergeSort(int a[],int left, int right){
	    if (left==right) {
	        return;
	    }
	    //分成两半
	    int mid = left + (right-left)/2;
	    //左边排序
	    MergeSort(a,left,mid);
	    //右边排序
	    MergeSort(a,mid+1,right);
	    //合并数组
	    merge(a,left,mid+1,right);
	}

/**
*	将两个有序的数组合并
*/
static void merge(int a[], int leftPtr, int rightPtr, int rightBound){
	    int mid = rightPtr - 1;
	    int i = leftPtr;
	    int j = rightPtr;
	    int k = 0;
	    int[] temp = new int[rightBound - leftPtr + 1];
	    while ( i <= mid && j <= rightBound ) {
	        if (a[i] <= a[j]) {
	            temp[k++] = a[i++];
	        }else{
	            temp[k++] = a[j++];
	        }
	    }
	    while (i <= mid) {
	        temp[k++] = a[i++];
	    }
	    while (j <= rightBound) {
	        temp[k++] = a[j++];
	    }
	   for (int l = 0; l < temp.length; l++) {
		   a[ l + leftPtr] = temp[l];
	   }
	}
  1. 堆排序(Heap)

  2. 希尔排序( Shell )
    优化的插入排序,希尔排序的发明使得我们突破了慢速排序时代,它的时间复杂度是O( n^1.3 ) ,不稳定。
    思路:
    希尔排序基于插入排序,它以一定的间隔(gap),并且是递减的,对数组进行插入排序,相较于插入排序,其移动次数更少,移动距离更短。

//二分
static void shell(int[] a){
		for (int gap = a.length/2; gap > 0; gap/=2) {
			for (int i = gap; i < a.length; i++) {
				for (int j = i; j > gap-1; j-=gap) {
					if (a[j] < a[j-gap]) {
						swap(a, j, j-gap);
					}
				}
			}
		}
	}

//Knuth序列
static void shell(int[] a){
		//Knuth序列效率要比二分高
		int h = 1;
		while (h <= a.length/3) {
			h = 3*h + 1;
		}
		for (int gap = h; gap > 0; gap=(gap+1)/3) {
			for (int i = gap; i < a.length; i++) {
				for (int j = i; j > gap-1; j-=gap) {
					if (a[j] < a[j-gap]) {
						swap(a, j, j-gap);
					}
				}
			}
		}
	}
  1. 桶排序( Bucket )

  2. 计数排序( Counting )
    桶思想的一种,非比较排序,适用于量大但范围小;它的时间复杂度是 O( n + k ) ,稳定。
    比如:某大型企业数万名员工年龄排序?如何快速得知高考名次?
    思路:
    遍历数组的值 x , 在 count [ x ] ++ ,再将count按出现的次数复制到原数组;

//version 1.0
static void counting(int[] a){
		int[] result = new int[a.length];
		int[] count = new int[61];
		for (int i = 0; i < a.length; i++) {
			count[ a[i] ]++;
		}
		
		int j=0;
		for (int i = 0; i < count.length; i++) {
			while (count[i]-- > 0) {
				result[j++] = i;
			}
		}
		for (int i = 0; i < result.length; i++) {
			a[i] = result[i];
		}
	}

到目前为止,计数排序version 1.0 有两大问题:其一,如果我们只要求排100~150,那么0到100的空间就会浪费;其二,算法不稳定。
对于空间的浪费,我们可以找出数组的最大和最小值;
对于算法的稳定性,解决的方法是累加数组count;

static void counting(int[] a){
		int max = a[0];
		int min = a[0];
		for (int i = 0; i < a.length; i++) {
			if (a[i] > max) {
				max = a[i];
			}
			if (a[i] < min) {
				min = a[i];
			}
		}
		int n = a.length;
		//这里k的大小是要排序的数组中,元素大小的极值差+1
		int k = max - min +1;
		int[] result = new int[n];
		int[] count = new int[k];
		for (int i = 0; i < n; i++) {
		//优化过的地方,减小了数组c的大小
			count[ a[i]-min ]++;
		}
		for (int i = 1; i < count.length; i++) {
			count[i] += count[i-1];
		}
		int j=0;
		for (int i = a.length-1; i >= 0; i--) {
			result[--count[a[i]-min] ] = a[i];
		}
		for (int i = 0; i < result.length; i++) {
			a[i] = result[i];
		}
	}
  1. 基数排序(Radix)
    桶思想的一种,非比较排序,它的时间复杂度是 O( n * k ) ,稳定。
    思路:
    将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。
 static void radix(int[] a){
		 //找到数组a的最大值
		 int max = a[0];
		 for (int i = 0; i < a.length; i++) {
			if (a[i] > max) {
				max = a[i];
			}
		}
		 int[] result = new int[a.length];
		//按位数从低到高对数组进行排序
		 for (int exp = 1; max/exp > 0; exp *= 10) {
			 int[] count = new int[10];
			 
			//这里的思想其实跟计数排序是一样的
			 for (int i = 0; i < a.length; i++) {
				count[(a[i]/exp)%10]++;
			}
			//累加数组count
			 for (int i = 1; i < count.length; i++) {
				count[i] += count[i-1];
			}
			 
			 for (int i = a.length-1; i >= 0 ; i--) {
				result[--count[(a[i]/exp)%10]] = a[i];
			}
			 for (int i = 0; i < result.length; i++) {
				a[i] = result[i];
			}
		}
		 
	 }

学习资料来自马士兵的算法课程

猜你喜欢

转载自blog.csdn.net/Jackponwong/article/details/89677831