基于java的十大经典排序算法


目录

  • 基于java的十大经典排序算法
  • 算法时间复杂度:
  • 1.冒泡排序
  • 2.选择排序
  • 3.插入排序
  • 4.希尔排序
  • 5.归并排序
  • 6.快速排序
  • 7.堆排序
  • 8.计数排序
  • 9.桶排序
  • 10.基数排序



这篇文章没有出现排序算法的可视化,推荐一个可视化排序算法的网站-------->

美国旧金山大学计算机科学


里边给出了很详细的过程,界面如下图


Java推荐算法 java十大经典算法_java

算法时间复杂度:

Java推荐算法 java十大经典算法_算法_02

1.冒泡排序

(1)思路:从头到尾,比较相邻元素,前面大就换,每次将尾减小一
(2)特点:最大数沉底
(3)时间复杂度:O(N²)
(4)辅助空间:无
(5)Java代码

package 十大经典排序算法;

import com.dc.Util;

public class _01BubbleSort {
	public static void main(String[] args) {
		// 自定义函数,生成一个100位0-99的数组
		int[] arr = Util.getRandomIntegerArrayWithoutRepetition(100, 0, 99);
		bubbleSort(arr);
	}

	// 冒泡排序(最大数沉底)
	public static void bubbleSort(int[] arr) {
		for (int i = 1; i < arr.length; i++) {
			for (int j = 0; j < arr.length - i; j++) {
				if (arr[j] > arr[j + 1]) {
					// 自定义函数,交换数组元素
					Util.swap(arr, j, j + 1);
				}
			}
		}
	}
}

2.选择排序

(1)思路:遍历数组找出最小的元素,放到当前头部,每次完成数组的遍历,头部向右移动一位。
(2)特点:最小值到头
(3)时间复杂度:O(N²)
(4)辅助空间:无
(5)Java代码

package 十大经典排序算法;

import com.dc.Util;

public class _02SelectionSort {
	public static void main(String[] args) {
		// 自定义函数,生成一个100位0-99的数组
		int[] arr = Util.getRandomIntegerArrayWithoutRepetition(100, 0, 99);
		selectionSort(arr);
	}

	// 选择排序
	public static void selectionSort(int[] arr) {
		// 定义最小元素的下标
		int minElementIndex;
		// 定义最小元素
		int number;
		for (int i = 0; i < arr.length - 1; i++) {
			// 将最小元素置为未排好序数组的第一个元素
			number = arr[i];
			// 将最小元素下标置为未排好序数组的第一个元素下标
			minElementIndex = i;
			for (int j = i + 1; j < arr.length; j++) {
				if (arr[j] < number) {
					// 更新最小元素
					number = arr[j];
					// 更新最小元素下标
					minElementIndex = j;
				}
			}
			if (i != minElementIndex) {
				// 自定义函数,交换数组的两个元素
				Util.swap(arr, i, minElementIndex);
			}
		}
	}
}

3.插入排序

(1)思路:从头到尾,每次从当前头部开始到数组开头,遇到前一个元素比后一个元素大,就交换,前一个元素比后一个元素小,就到位了
(2)特点:对于每一个 i 都能达到前面有序
(3)时间复杂度:O(N²)
(4)辅助空间:无
(5)Java代码

package 十大经典排序算法;

import com.dc.Util;

public class _03InsertSort {
	public static void main(String[] args) {
		// 自定义函数,生成100位1-1000的数组
		int[] arr = Util.getRandomIntegerArray(100, 1, 1000);
		insertSort(arr);
	}

	/**
	 * 插入排序
	 * @param arr
	 */
	public static void insertSort(int[] arr) {
		for (int i = 1; i < arr.length; i++) {
			// 定义当前元素
			int current = arr[i];
			// 定义待比较的较小元素下标
			int j = i - 1;
			// 只要没有遇到比较小元素就一直循环,遇到了就说明到应该到的位置上了,即终止循环
			while (j > -1 && arr[j] > current) {
				arr[j + 1] = arr[j];
				// 修改两个对比的元素,继续循环
				j--;
			}
			// 将原来arr[j]位置上的元素置为原来arr[j+1]位置上的元素
			arr[j + 1] = current;
		}
	}
}

4.希尔排序

(1)思路:缩小增量排序,以增量位标准进行分组,对每一组进行插入排序,直到增量减少为一
(2)特点:改良版的插入排序,看似在插入排序外部再套了一层循环,却加快了排序速度
(3)时间复杂度:O(N的1.3次方)
(4)辅助空间:无
(5)Java代码

package 十大经典排序算法;

import com.dc.Util;

public class _04ShellSort {
	public static void main(String[] args) {
		// 自定义函数,生成一个100位0-99的数组
		int[] arr = Util.getRandomIntegerArrayWithoutRepetition(100, 0, 99);
		shellSort(arr);
	}

	/**
	 * 希尔排序
	 * 缩小增量排序
	 * @param arr
	 */
	public static void shellSort(int[] arr) {
		// 以增量为数组长度的一半为起点,缩小增量,每次减少为原来的一半
		for (int interval = arr.length >> 1; interval > 0; interval = interval >> 1) {
			// 从增量开始,到达数组长度
			for (int i = interval; i < arr.length; i++) {
				// 记录当前待比较的元素
				int current = arr[i];
				// 记录数组中上一个interval的元素的下标
				int j = i - interval;
				while (j > -1 && arr[j] > current) {
					// 前面的元素大就发生交换
					arr[j + interval] = arr[j];
					// 更新下一次比较中,下一个待比较元素的下标
					j -= interval;
				}
				// 更新下一次比较中,当前待比较元素的值
				arr[j + interval] = current;
			}
		}
	}
}

5.归并排序

(1)思路:两个身高由低到高已经拍好序的队伍,依次过球门,每次都是两个队伍中较矮的人先过去
(2)特点:重视合并过程,简化划分过程
(3)时间复杂度:O(NlgN) ----> 每一个数组元素都要过球门需要N次,每次操作对象大小对半分,需要lgN,O(NlgN)
(4)辅助空间:O(N) -----> 开辟长度为N的辅助数组,接收原数组
(5)Java代码

package 十大经典排序算法;

import com.dc.Util;

public class _05MergeSort {
	static final int MAX_VALUE = 1000;
	static int[] helper = new int[MAX_VALUE];

	public static void main(String[] args) {
		// 自定义函数,生成数组
		int[] arr = Util.getRandomIntegerArrayWithoutRepetition(MAX_VALUE, 0, 999);
		mergeSort(arr, 0, arr.length - 1);
	}

	/**
	 * 归并排序
	 * 需要开辟辅助空间
	 * 两个已经排好的队伍过球门模型
	 * @param arr
	 * @param begin	待排序数组的起始位置
	 * @param end	待排序数组的终点位置
	 */
	public static void mergeSort(int[] arr, int begin, int end) {
		if (begin < end) {
			// 定义中间位置的元素下标
			int mid = (begin + end) >> 1;
			// 左排序
			mergeSort(arr, begin, mid);
			// 右排序
			mergeSort(arr, mid + 1, end);
			// 归并
			merge(arr, begin, mid, end);
		}
	}

	public static void merge(int[] arr, int begin, int mid, int end) {
		// 将arr数组中的元素拷贝到helper数组中
		System.arraycopy(arr, begin, helper, begin, end - begin + 1);
		// 定义左指针
		int left = begin;
		// 定义右指针
		int right = mid + 1;
		// 定义对原数组arr进行覆盖时的指针
		int current = begin;
		// 左边还没到头,且右边还没到头时就一直循环
		while (left <= mid && right <= end) {
			// 左边元素小于右边,那么就左边走
			if (helper[left] < helper[right]) {
				// 将较小元素覆盖给原数组arr
				arr[current] = helper[left];
				// 覆盖指针右移
				current++;
				// 左指针右移
				left++;
			} else {
				// 将较小元素覆盖给原数组arr
				arr[current] = helper[right];
				// 覆盖指针右移
				current++;
				// 右指针右移
				right++;
			}
		}
		// 当左半边的元素没有全部覆盖到原数组时,需要继续进行覆盖,而右半边的元素没有全部覆盖时,则不用处理,
		// 因为在进行helper数组的拷贝时已经将元素处理到位了
		while (left <= mid) {
			// 直接覆盖
			arr[current] = helper[left];
			// 覆盖指针右移
			current++;
			// 左指针右移
			left++;
		}
	}
}

6.快速排序

(1)单向扫描分区算法思路:定主元,定左右两个指针,以左指针为核心,只要左指针对应的元素比主元小(或等于),左指针右移,否则交换左右指针对应元素,右指针左移,最后左右指针一定交错,且右指针在左指针的左边,此时交换右指针对应的元素和主元即可。
(2)双线扫描分区算法思路:定主元,定左右两个指针,左右指针同时配合,左指针只要不遇到比主元大的就一直右移,右指针只要不遇到比主元小的就一直左移,直到左右指针交错。
(3)时间复杂度:O(NlgN)
(4)Java代码

package 十大经典排序算法;

import com.dc.Util;

public class _06QuickSort {
	public static void main(String[] args) {
		// 自定义函数,生成一个10位0-99的数组
		int[] arr = Util.getRandomIntegerArray(10, 0, 99);
		long now = System.currentTimeMillis();
		quickSort(arr, 0, arr.length - 1);
		// 自定义函数,测试程序运行时间
		Util.timeNeed(now);
	}

	/**
	 * 快速排序的两种实现
	 * 单向扫描分区算法
	 * 双向扫描分区算法
	 * @param arr
	 * @param begin	待排序的起始位
	 * @param end	待排序的终止位
	 */
	public static void quickSort(int[] arr, int begin, int end) {
		if (begin < end) {
//			int newPivot = one_wayPatition(arr, begin, end);
			// 定义基元
			int newPivot = both_wayPatition(arr, begin, end);
			// 左排序
			quickSort(arr, begin, newPivot - 1);
			// 右排序
			quickSort(arr, newPivot + 1, end);
		}

	}

	/**
	 * 单向扫描分区算法
	 * @return
	 */
	public static int one_wayPatition(int[] arr, int begin, int end) {
		// 定义主元
		int pivot = arr[begin];
		// 定义左指针
		int left = begin + 1;
		// 定义右指针
		int right = end;
		while (left <= right) {
			// 较小,左指针右移
			if (arr[left] <= pivot) {
				left++;
			} else {
				// 自定义函数,交换数组元素
				Util.swap(arr, left, right);
				right--;
			}
		}
		// 交换主元和右指针对应元素的值
		Util.swap(arr, begin, right);
		// 返回移动后的主元位置
		return right;
	}

	/**
	 * 双向扫描分区算法
	 * @return 返回移动后的主元位置
	 */
	public static int both_wayPatition(int[] arr, int begin, int end) {
		// 定义主元
		int pivot = arr[begin];
		// 定义左指针
		int left = begin + 1;
		// 定义右指针
		int right = end;
		while (left <= right) {
			// 没越界且元素较小,左指针右移,循环结束代表遇到了较大或者等于的元素
			while (left <= right && arr[left] <= pivot) {
				left++;
			}
			// 没越界且元素较大,右指针左移,循环结束代表遇到了较小元素
			while (left <= right && arr[right] > pivot) {
				right--;
			}
			if (left < right) {
				Util.swap(arr, left, right);
			}
		}
		// 交换主元和右指针对应元素的值
		Util.swap(arr, begin, right);
		// 返回移动后的主元的下标
		return right;
	}
}

7.堆排序

(1)思路:将数组堆化,形成一个大顶堆(对应正序输出)或者小顶堆(对应逆序输出),每次将堆顶元素和最后一个元素交换,并将堆的规模缩小一,直到完成排序。
(2)特点:利用递归反复生成大小顶堆,取出根节点
(3)时间复杂度:O(NlgN)
(4)辅助空间:无
(5)Java代码

小顶堆 -----> 逆序输出

package 十大经典排序算法;

import com.dc.Util;

public class _07HeapSort_Min {
	public static void main(String[] args) {
		// 自定义函数,用于获取一个1000位0-999的数组(无重复元素)。
		int[] arr = Util.getRandomIntegerArrayWithoutRepetition(1000, 0, 999);
		heapSort(arr);
		// 自定义函数,输出数组
		Util.printIntegerArray(arr);
	}

	/**
	 * 堆排序(小顶堆) ---> 逆序输出
	 * @param arr
	 */
	public static void heapSort(int[] arr) {
		// 将数组堆化(产生一个完全小顶堆) ---> 根节点为最小元素
		makeMinHeap(arr);
		// 从尾部开始一次递减
		for (int i = arr.length - 1; i >= 0; i--) {
			// 自定义函数,交换根节点(最小值)和当前最后一个节点的值 ---> 数组的最后一个元素为最小值
			Util.swap(arr, 0, i);
			// 按照此过程,依次缩小堆的规模,每次数组最后一个元素都为当前最小值
			operateHeap(arr, 0, i);
		}
	}

	/**
	 * 将数组堆化,生成一个完全小顶堆
	 * @param arr
	 */
	public static void makeMinHeap(int[] arr) {
		int length = arr.length;
		// 从倒数第二排最后一个元素开始,向根节点进行操作
		for (int i = length >> 1 - 1; i >= 0; i--) {
			operateHeap(arr, i, length);
		}
	}

	/**
	 * 对每一个子堆进行操作,得到小顶堆
	 * @param arr
	 * @param i	待堆化的子根节点
	 * @param length 子树当前的规模
	 */
	public static void operateHeap(int[] arr, int i, int length) {
		// 定义左节点
		int left = 2 * i + 1;
		// 定义右节点
		int right = 2 * i + 2;
		// 没有左节点-->当前i节点为叶子节点
		if (left >= length) {
			return;
		}
		// 有左节点,判断有无右节点,有右节点则判断左右节点的大小,选出其中小的节点
		int min = left;
		if (right >= length) {
			;
		} else {
			if (arr[right] < arr[left]) {
				min = right;
			}
		}
		// 此时,min指向较小的节点
		if (arr[i] > arr[min]) {
			// 如果小的节点比i节点小就交换
			Util.swap(arr, i, min);
			// 并且对以min节点为子根节点的子堆进行建立小顶堆操作
			operateHeap(arr, min, length);
		}
	}

}

大顶堆 -----> 正序输出

package 十大经典排序算法;

import com.dc.Util;

public class _07HeapSort_Max {
	public static void main(String[] args) {
		// 自定义函数,用于获取一个1000位0-999的数组(无重复元素)。
		int[] arr = Util.getRandomIntegerArrayWithoutRepetition(1000, 0, 999);
		heapSort(arr);
		// 自定义函数,输出数组
		Util.printIntegerArray(arr);
	}

	/**
	 * 堆排序(大顶堆)---->  正序输出
	 * @param arr
	 */
	public static void heapSort(int[] arr) {
		// 将数组堆化,并且转化为大顶堆,转化之后,根节点为最大值
		makeMaxHeap(arr);
		for (int i = arr.length - 1; i >= 0; i--) {
			// 自定义函数,用于交换数组的两个对应下标的元素,交换根节点的值(最大值)与数组最后一个元素的值
			Util.swap(arr, 0, i);
			// 缩小大顶堆的规模,每次形成一个大顶堆,每次交换根节点的值(最大值)和当前最后一个元素的值(每次减小一个)
			operateMaxHeap(arr, 0, i);
		}
	}

	/**
	 * 创建大顶堆
	 * @param arr
	 */
	public static void makeMaxHeap(int[] arr) {
		int length = arr.length;
		// 从倒数第二排最后一个有子节点的节点开始,自底向上依次生成大顶堆,方法执行完之后,形成一个完全大顶堆
		for (int i = length >> 1 - 1; i >= 0; i--) {
			operateMaxHeap(arr, i, length);
		}
	}

	/**
	 * 操作大顶堆,先找左右子节点
	 * @param arr
	 * @param i	当前参照的子根节点
	 * @param n 子树当前的规模
	 */
	public static void operateMaxHeap(int[] arr, int i, int n) {
		// 定义左节点
		int left = 2 * i + 1;
		// 定义右节点
		int right = 2 * i + 2;
		// 若没有左节点,那么当前节点为叶子节点
		if (left >= n) {
			return;
		}
		// 有左节点则判断有无右节点,有右节点则判断左右节点哪一个较大,选出较大的节点
		int max = left;
		if (right >= n) {
			;
		} else {
			if (arr[right] >= arr[left]) {
				max = right;
			}
		}
		// 此时max指向两个孩子节点中较大的一个
		if (arr[max] > arr[i]) {
			// 自定义函数,用于交换数组的两个对应下标的元素, 交换max指向的节点和当前根节点
			Util.swap(arr, max, i);
			// 因为改变了子节点,所以需要让以该子节点为根节点的子树形成大顶堆
			operateMaxHeap(arr, max, n);
		}
	}
}

8.计数排序

(1)思路:计数排序思路很简单,元素转下标,下标转元素。计算出数组中最大的值,开辟一般大的辅助空间,遍历数组,只要将数组元素转化为辅助数组的下标,如原数组出现2,那么对应辅助数组下标为2的元素加一,直到遍历完原数组。最后遍历一遍辅助数组,同时对原数组覆盖,实现原数组有序。
(2)特点:优点时很快,但是如果数组元素分布过于稀疏,将会造成很大的空间浪费。
(3)时间复杂度:O(N)
(4)辅助空间:O(max) -----> max 为数组元素最大值
(5)Java代码

package 十大经典排序算法;

import com.dc.Util;

public class _08CountingSort {
	public static void main(String[] args) {
		// 自定义函数,用于获取一个1000位0-999的数组(无重复元素)。
		int[] arr = Util.getRandomIntegerArrayWithoutRepetition(1000, 0, 999);
		countingSort(arr);
		// 自定义函数,输出数组
		Util.printIntegerArray(arr);
	}

	/**
	 * 计数排序
	 * 通过开辟辅助空间实现元素转下标,再下标转元素
	 * 优点:速度很快
	 * 缺点:需要开辟辅助空间,对应数据分布很稀疏的数组,会造成很大的空间浪费
	 * @param arr
	 */
	public static void countingSort(int[] arr) {
		// 自定义函数,求数组的最大元素
		int maxElement = Util.maxElementOfArray(arr);
		// 开辟辅助空间,实现元素转下标
		int[] helper = new int[maxElement + 1];
		// 定义对原数组进行覆盖时的覆盖指针
		int current = 0;
		// 元素转下标
		for (int i = 0; i < arr.length; i++) {
			helper[arr[i]]++;
		}
		// 下标转元素
		for (int i = 0; i < helper.length; i++) {
			while (helper[i] != 0) {
				// 下标转元素
				arr[current++] = i;
				// helper对应元素减一
				helper[i]--;
			}
		}
	}
}

9.桶排序

(1)思路:桶排序思路和计数排序相似,都是涉及入桶、出桶操作。依据某种算法将原数组元素分配到对应的桶中,并使得每一个桶有序,再依次遍历所有桶,完成出桶操作。下面的java代码使用的算法时,index = value * n / (max + 1);
其中:index为需要入的桶的下标
value: 为当前元素的值
n: 为数组的长度
max: 为数组元素的最大值
可以看出只需要n个桶就能完成数组的排序
(2)特点:快,而且不像计数排序一样需要消耗很大的辅助空间,只需要N个桶
(3)时间复杂度:O(N+k)
(4)辅助空间:O(N+k)
(5)Java代码

package 十大经典排序算法;

import java.util.ArrayList;

import com.dc.Util;

public class _09BucketSort {
	public static void main(String[] args) {
		// 自定义函数,用于获取一个1000位1-999的数组。
		int[] arr = Util.getRandomIntegerArray(1000, 1, 999);
		bucketSort(arr);
		// 自定义函数,用于输出数组
		Util.printIntegerArray(arr);
	}

	/**
	 * 初次写的桶排序(不完美)
	 * @param arr
	 */
	public static void bucketSort(int[] arr) {
		@SuppressWarnings("unchecked")
		// 开辟辅助列表数组
		ArrayList<Integer>[] bucket = new ArrayList[arr.length];
		// 初始化列表数组
		for (int i = 0; i < arr.length; i++) {
			bucket[i] = new ArrayList<Integer>();
		}
		// 自定义函数,用于求数组最大值,定义数组的最大值
		int max = Util.maxElementOfArray(arr);
		// 定义桶的下标
		int index = 0;
		// 提前计算数组的长度,避免反复计算
		int length = arr.length;
		// 提前计算除数
		int max2 = max + 1;
		for (int i = 0; i < arr.length; i++) {
			// 计算元素需要插入的桶的下标
			index = arr[i] * length / max2;
			// 入桶操作
			bucket[index].add(arr[i]);
			// 排序
			bucket[index].sort(null);
		}
		// 定义覆盖原数组时的覆盖指针
		int current = 0;
		// 遍历列表数组
		for (int i = 0; i < arr.length; i++) {
			// 遍历列表数组中的单个列表
			for (Integer in : bucket[i]) {
				// 对原数组进行覆盖
				arr[current++] = in;
			}
		}
	}
}

10.基数排序

(1)思路:基数排序的思路类似于桶排序,只是将桶的数量定为十个,同时改变相应的入桶规则,就能实现排序。先算出数组的最大值,计算最大值的位数,以此确定需要循环的次数,在每一次循环中,依据数组元素对应位数上的数字进行入桶操作,如对于数字284,如果本次循环依据的是第二位,即十位数,那么对应的数字为8,就将284放入第8号桶,重复此过程k次(k为最大数的位数)就能实现数组有序。
(2)特点:依据数组元素对应位上的数字对数组元素进行入桶操作。
(3)时间复杂度:O(N*k)
(4)辅助空间:O(N+10)
(5)Java代码

package 十大经典排序算法;

import java.util.ArrayList;

import com.dc.Util;

public class _10RadixSort {
	@SuppressWarnings("unchecked")
	// 定义全局变量列表数组
	private static ArrayList<Integer>[] bucket = new ArrayList[10];
	// 静态初始化列表数组
	static {
		for (int i = 0; i < bucket.length; i++) {
			bucket[i] = new ArrayList<Integer>();
		}
	}

	public static void main(String[] args) {
		// 自定义函数,用于获取一个100000000位0-999的数组。
		int[] arr = Util.getRandomIntegerArray(100000000, 0, 999);
//		Util.printIntegerArray(arr);
		long now = System.currentTimeMillis();
		radixSort(arr);
		// 自定义函数,用于求程序运行时间
		Util.timeNeed(now);
//		Util.printIntegerArray(arr);
	}

	/**
	 * 基数排序
	 * @param arr
	 */
	public static void radixSort(int[] arr) {
		// 自定义函数,用于求数组的最大值,定义数组最大值
		int max = Util.maxElementOfArray(arr);
		// 定义最大元素的位数
		int digitOfMax = 1;
		while (max / 10 != 0) {
			digitOfMax++;
			max /= 10;
		}
		// 按照每一位进行入桶出桶,覆盖操作
		for (int i = 1; i <= digitOfMax; i++) {
			sort(arr, i);
		}
	}

	/**
	 * 按照第k位对数组元素进行入桶操作
	 * 数组的入桶,出桶,原数组的覆盖,和桶的清除操作
	 * @param arr
	 * @param k	依照的位数
	 */
	public static void sort(int[] arr, int k) {
		// 数组中所有元素的入桶操作
		for (int i = 0; i < arr.length; i++) {
			putInBucket(arr[i], numberOfIndex(arr[i], k));
		}
		// 定义覆盖数组时的覆盖指针
		int current = 0;
		// 遍历列表数组对原数组进行覆盖
		for (int i = 0; i < bucket.length; i++) {
			for (Integer in : bucket[i]) {
				arr[current++] = in;
			}
		}
		// 每次出桶操作之后,进行桶的清除操作
		clearBucket();
	}

	/**
	 * 入桶操作
	 * @param element	待入桶元素
	 * @param number	待入桶元素应该入的桶的位置
	 */
	public static void putInBucket(int element, int number) {
		switch (number) {
		case 0:
			bucket[0].add(element);
			break;
		case 1:
			bucket[1].add(element);
			break;
		case 2:
			bucket[2].add(element);
			break;
		case 3:
			bucket[3].add(element);
			break;
		case 4:
			bucket[4].add(element);
			break;
		case 5:
			bucket[5].add(element);
			break;
		case 6:
			bucket[6].add(element);
			break;
		case 7:
			bucket[7].add(element);
			break;
		case 8:
			bucket[8].add(element);
			break;
		default:
			bucket[9].add(element);
			break;
		}
	}

	/**
	 * 返回一个元素第k位的数字
	 * @param element	待操作元素
	 * @param k	第k位
	 * @return 元素的第k位
	 */
	public static int numberOfIndex(int element, int k) {
		int N = (int) (element / Math.pow(10, k - 1) % 10);
		return N;
	}

	/**
	 * 桶的清除
	 */
	public static void clearBucket() {
		for (int i = 0; i < bucket.length; i++) {
			bucket[i].clear();
		}
	}
}