【算法编程】排序算法详解

一、排序算法复杂度比较

T(N) = a*T(N/b) + O(N^d)

  • log(b,a)>dlog(b,a) > d -> 复杂度为O(Nlog(b,a))O(N^{log(b,a)})
  • log(b,a)=dlog(b,a) = d -> 复杂度为O(NdlogN)O(N^d * logN)
  • log(b,a)<dlog(b,a) < d -> 复杂度为O(Nd)O(N^d)

二、冒泡排序

  • 时间复杂度 O(N2)O(N^2),额外空间复杂度 O(1)O(1)
  • 可实现成稳定的
    在这里插入图片描述

经过上图一轮后,最大的数就到了最后位置,接下来我们对剩下的1,2,3,6,4进行同样的方式,最终可以将数值从小到大排序

Java代码:

package day01;

/**
 * 冒泡排序
 * @author Danycym
 *
 */
public class Code01_BubbleSort {
	public static void bubbleSort(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		for (int i = arr.length - 1; i > 0; i--) {
			for (int j = 0; j < i; j++) {
				if (arr[j] > arr[j + 1]) {
					swap(arr, j, j + 1);
				}
			}
		}
	}
	
	public static void swap(int[] arr, int i, int j) {
		int tmp = arr[i];
		arr[i] = arr[j];
		arr[j] = tmp;
	}
	public static void printArray(int[] arr) {
		if (arr == null) {
			return;
		}
		for (int i = 0; i < arr.length; i++) {
			System.out.print(arr[i] + " ");
		}
		System.out.println();
	}
	public static void main(String[] args) {
		int[] arr = {2, 1, 4, 3, 6, 5, 9, 8, 7};
		printArray(arr);
		bubbleSort(arr);
		printArray(arr);
	}
}

动图参考来自网络:
在这里插入图片描述

三、插入排序

  • 时间复杂度 O(N2)O(N^2),额外空间复杂度 O(1)O(1)
  • 可实现成稳定的

在这里插入图片描述

插入排序和我们平时插扑克牌类似,上图中橙色框内表示手里的牌,橙色框下一个元素表示下一张要抓的牌,每抓到一张新的牌,我们就将其插入到手里对应的位置(这里需要从新牌的上一张到第一张逐一比较,比手里的牌小就交换,直到比手里的牌大,此时插入到小牌后面,大牌前面完成插入),依次类推,直到最后一张牌

Java代码:

package day01;

/**
 * 插入排序
 * @author Danycym
 *
 */
public class Code02_InsertionSort {
	public static void insertionSort(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		for(int i = 1; i < arr.length; i++) {
			for(int j = i - 1; j >= 0; j--) {
				if(arr[j] > arr[j+1]) {
					swap(arr, j, j + 1);
				}
			}
		}
	}
	
	public static void swap(int[] arr, int i, int j) {
		int tmp = arr[i];
		arr[i] = arr[j];
		arr[j] = tmp;
	}
	public static void printArray(int[] arr) {
		if (arr == null) {
			return;
		}
		for (int i = 0; i < arr.length; i++) {
			System.out.print(arr[i] + " ");
		}
		System.out.println();
	}
	public static void main(String[] args) {
		int[] arr = {2, 1, 4, 3, 6, 5, 9, 8, 7};
		printArray(arr);
		insertionSort(arr);
		printArray(arr);
	}

}

动图参考来自网络:

在这里插入图片描述

四、选择排序

  • 时间复杂度 O(N2)O(N^2),额外空间复杂度 O(1)O(1)
  • 不可实现成稳定的
    在这里插入图片描述

每次设定橙色框位置为最小值,如果该最小值后面的数有比它小的就和它交换

Java代码:

package day01;

/**
 * 选择排序
 * @author Danycym
 *
 */
public class Code03_SelectionSort {
	public static void selectionSort(int[] arr) {
		if(arr == null || arr.length < 2) {
			return;
		}
		
		for(int i = 0; i < arr.length - 1; i++) {
			int minIndex = i;
			for(int j = i + 1; j < arr.length; j++) {
				minIndex = arr[j] < arr[minIndex] ? j : minIndex;
			}
			swap(arr, i, minIndex);
		}
	}
	public static void swap(int[] arr, int i, int j) {
		int tmp = arr[i];
		arr[i] = arr[j];
		arr[j] = tmp;
	}
	public static void printArray(int[] arr) {
		if(arr == null) {
			return;
		}
		for(int i = 0; i < arr.length; i++) {
			System.out.print(arr[i] + " ");
		}
		System.out.println();
	}
	public static void main(String[] args) {
		int[] arr = {2, 1, 4, 3, 6, 5, 9, 8, 7};
		printArray(arr);
		selectionSort(arr);
		printArray(arr);
	}
}

动图参考来自网络:

在这里插入图片描述

五、归并排序

  • 时间复杂度 O(NlogN)O(N*logN),额外空间复杂度 O(N)O(N)
  • 可实现成稳定的
    在这里插入图片描述

将原问题拆分成子问题,再对子问题进行同样的操作,直到不能再分,然后就是排序合并

Java代码:

package day01;

/**
 * 归并排序
 * @author Danycym
 *
 */
public class Code04_MergeSort {
	public static void mergeSort(int[] arr) {
		if(arr == null || arr.length < 2) {
			return;
		}
		mergeSort(arr, 0, arr.length - 1);
	}
	private static void mergeSort(int[] arr, int l, int r) {
		if(l == r) {
			return;
		}
		int mid = l + ((r - l) >> 1);  //中点位置
		mergeSort(arr, l, mid);  //左分支
		mergeSort(arr, mid + 1, r);  //右分支
		merge(arr, l, mid, r);  //合并
	}
	private static void merge(int[] arr, int l, int m, int r) {
		int[] help = new int[r - l + 1];  //存储合并结果
		int i = 0;
		int p1 = l;
		int p2 = m + 1;
		//比较大小,依次添加到help中
		while(p1 <= m && p2 <= r) {
			help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
		}
		//p1还没到最后,直接将后面的都添加到help
		while(p1 <= m) {
			help[i++] = arr[p1++];
		}
		//p2还没到最后,直接将后面的都添加到help
		while(p2 <= r) {
			help[i++] = arr[p2++];
		}
		//最后将help中的元素都拷贝到原数组中
		for(i = 0; i < help.length; i++) {
			arr[l + i] = help[i];
		}
	}
	public static void printArray(int[] arr) {
		if(arr == null) {
			return;
		}
		for(int i = 0; i < arr.length; i++) {
			System.out.print(arr[i] + " ");
		}
		System.out.println();
	}
	public static void main(String[] args) {
		int[] arr = {2, 1, 4, 3, 6, 5, 9, 8, 7};
		printArray(arr);
		mergeSort(arr);
		printArray(arr);
	}
}

动图参考来自网络:

在这里插入图片描述

六、快速排序

  • 时间复杂度 O(NlogN)O(N*logN),额外空间复杂度 O(logN)O(logN)
  • 不可实现成稳定的

在这里插入图片描述

上图中,我们选择最后一个元素作为划分的那个值,程序中做了修改,每次随机选择一个数作为划分的数,具体做法如下:

  • 先随机选择一个元素,然后将它与最后一个元素交换,最后在进行看划分
  • swap(arr, l + (int)(Math.random() * (r - l + 1)), r);

Java代码:

package day01;
/**
 * 快速排序
 * @author Danycym
 *
 */
public class Code05_QuickSort {

	public static void quickSort(int[] arr) {
		if(arr == null || arr.length < 2) {
			return;
		}
		quickSort(arr, 0, arr.length - 1);
	}
	
	public static void quickSort(int[] arr, int l, int r) {
		if(l < r) {
			//随机选择一个元素作为划分的数
			swap(arr, l + (int)(Math.random() * (r - l + 1)), r);
			int[] p = partition(arr, l, r);
			quickSort(arr, l, p[0] - 1);
			quickSort(arr, p[1] + 1, r);
		}
	}
	//交换
	public static void swap(int[] arr, int i, int j) {
		int tmp = arr[i];
		arr[i] = arr[j];
		arr[j] = tmp;
	}
	//划分
	public static int[] partition(int[] arr, int l, int r) {
		int less = l - 1;
		int more = r;
		while(l < more) {
			if(arr[l] < arr[r]) {//遍历到的元素比最后一个元素小
				swap(arr, ++less, l++);//左边界往右扩大1,并与当前遍历到的元素交换
			}else if(arr[l] > arr[r]) {//遍历到的元素比最后一个元素大
				swap(arr, --more, l);//右边界往左扩大1,并与当前遍历到的元素交换
			}else {//否则,直接遍历下个元素
				l++;
			}
		}
		swap(arr, more, r);//交换arr[more]与arr[r]
		return new int[] {less + 1, more};
	}
	public static void printArray(int[] arr) {
		if(arr == null) {
			return;
		}
		for(int i = 0; i < arr.length; i++) {
			System.out.print(arr[i] + " ");
		}
		System.out.println();
	}
	public static void main(String[] args) {
		int[] arr = {2, 1, 4, 3, 6, 5};
		printArray(arr);
		quickSort(arr);
		printArray(arr);
	}

}

动图参考来自网络:

在这里插入图片描述

黄色代表基数(划分的数)
红色代表当前遍历的
绿色代表小于基数的
紫色代表大于基数的
橙色代表排序好的

  • 该图每次都是以第一个元素作为划分基数的

七、堆排序

  • 时间复杂度 O(NlogN)O(N*logN),额外空间复杂度 O(1)O(1)
  • 不可实现成稳定的

数组理解为完全二叉树:
在这里插入图片描述

大根堆:

  • 堆就是完全二叉树
  • 大根堆:在这棵完全二叉树中,任何一棵子树的最大值都是该子树的头部

在这里插入图片描述

将数组脑补的完全二叉树转换为大根堆:

在这里插入图片描述

堆排序:
在这里插入图片描述

Java代码:

package day01;
/**
 * 堆排序
 * @author Danycym
 *
 */
public class Code06_HeapSort {
	public static void heapSort(int[] arr) {
		if(arr == null || arr.length < 2) {
			return;
		}
		//构建大根堆
		for(int i = 0; i < arr.length; i++) {
			heapInsert(arr, i);
		}
		//大根堆长度
		int heapSize = arr.length;
		//将大根堆第一个元素与此时剩下的大根堆的最后一个元素交换
		swap(arr, 0, --heapSize);
		while(heapSize > 0) {
			heapify(arr, 0, heapSize);  //进行下沉操作
			swap(arr, 0, --heapSize);
		}
	}
	
	public static void heapInsert(int[] arr, int i) {
		while(arr[i] > arr[(i -1) / 2]) {  //如果i对应的值大于其父节点
			swap(arr, i, (i -1) / 2);  //则和父节点交换
			i = (i -1) / 2;  //并将父节点位置更新为当前遍历
		}
	}
	//
	public static void heapify(int[] arr, int i, int heapSize) {
		int left = i * 2 + 1; //左孩子下标
		while(left < heapSize) {
			//判断左右孩子中较大的孩子的下标
			int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;
			//再将较大者和当前遍历比较,记录较大者下标
			largest = arr[largest] > arr[i] ? largest : i;
			if(largest == i) {  //如果相等,较大者是自己,退出
				break;
			}
			//否则交换
			swap(arr, largest, i);
			i = largest;  //更新i为交换后的位置
			left = i * 2 + 1;
		}
	}
	//交换
	public static void swap(int[] arr, int i, int j) {
		int tmp = arr[i];
		arr[i] = arr[j];
		arr[j] = tmp;
	}

	//打印
	public static void printArray(int[] arr) {
		if(arr == null) {
			return;
		}
		for(int i = 0; i < arr.length; i++) {
			System.out.print(arr[i] + " ");
		}
		System.out.println();
	}
	
	public static void main(String[] args) {
		int[] arr = {2, 1, 4, 3, 6, 5};
		printArray(arr);
		heapSort(arr);
		printArray(arr);
	}

}

动图参考来自网络:

在这里插入图片描述

八、桶排序

  • 时间复杂度 O(N)O(N),额外空间复杂度 O(N)O(N)
  • 可以实现成稳定的

桶排序是一种非基于比较的排序,其实现包括计数排序和基数排序两种,其基本思想如下:

  1. 得到无序数组的取值范围
  2. 根据取值范围创建对应数量的桶
  3. 遍历数组,将每个元素放到对应的桶中
  4. 按照顺序遍历桶中的每个元素,依次放到数组中,即可完成数组的排序

在这里插入图片描述

堆排序是否稳定取决于“桶”用的是什么数据结构,如果是队列,那么可以保证相同的元素取出后的相对位置与“放进去”之前是相同的,如果用栈来实现桶,就不是稳定的
“桶”是一种容器,这个容器可以用多种数据结构实现,包括数组、队列或者栈

1、计数排序

  • 桶排序的实现之计数排序

在这里插入图片描述

Java代码:

package day01;
/**
 * 桶排序
 * @author Danycym
 *
 */
public class Code07_BucketSort {
	public static void bucketSort(int[] arr) {
		if(arr == null || arr.length < 2) {
			return;
		}
		int max = Integer.MIN_VALUE;
		//求数组的最大值
		for(int i = 0; i < arr.length; i++) {
			max = Math.max(max, arr[i]);
		}
		int[] bucket = new int[max + 1];
		for(int i = 0; i < arr.length; i++) {
			bucket[arr[i]]++;
		}
		int i = 0;
		for(int j = 0; j < bucket.length; j++) {
			while(bucket[j]-- > 0) {
				arr[i++] = j;
			}
		}
	}
	
	public static void printArray(int[] arr) {
		if(arr == null) {
			return;
		}
		for(int i = 0; i < arr.length; i++) {
			System.out.print(arr[i] + " ");
		}
		System.out.println();
	}
	
	public static void main(String[] args) {
		int[] arr = {2, 1, 12, 1, 3, 3, 4};
		printArray(arr);
		bucketSort(arr);
		printArray(arr);
	}
}

2、基数排序

待更新。。。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章