基于java的十大经典排序算法
目录
- 基于java的十大经典排序算法
- 算法时间复杂度:
- 1.冒泡排序
- 2.选择排序
- 3.插入排序
- 4.希尔排序
- 5.归并排序
- 6.快速排序
- 7.堆排序
- 8.计数排序
- 9.桶排序
- 10.基数排序
这篇文章没有出现排序算法的可视化,推荐一个可视化排序算法的网站-------->
美国旧金山大学计算机科学
里边给出了很详细的过程,界面如下图
算法时间复杂度:
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();
}
}
}