前言
八大排序,三大查找是《数据结构》当中非常基础的知识点,在这里为了复习顺带总结了一下常见的八种排序算法。
常见的八大排序算法,他们之间关系如下:
复杂度对比:
1. 冒泡排序
冒泡排序有两个版本,一个是原始版本,第二个是性能优化版本代码如下:
// 原始版本
// 较大的数据向后排
public static void bubbleSort1(int[] arr) {
int temp = 0;
int len = arr.length;
for (int i = 0; i < len - 1; i++) {
for (int j = 0; j < len - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
// 优化版本
public static void bubbleSort2(int[] arr) {
int temp = 0;
boolean flag = false;// 标记
int len = arr.length;
for (int i = 0; i < len - 1; i++) {
for (int j = 0; j < len - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
flag = true;
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
if (flag) {
flag = false;
} else {
break;// 再一趟对比中,一次交换都没有发生过,表示前面都是有序的
}
}
}
2. 选择排序
小数据往前排
public static void sort(int[] arr) {
int temp = 0;
int len = arr.length;
for (int i = 0; i < len - 1; i++) {
for (int j = i + 1; j < len; j++) {
if (arr[i] > arr[j]) {
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
}
3. 插入排序
public static void insertSort(int[] arr) {
// 默认情况下把数据第一个数字当成有序的,后面的无序往前面插入
for (int i = 1; i < arr.length; i++) {
int val = arr[i];
int index = i - 1;
while (index > -1 && arr[index] > val) {
arr[index + 1] = arr[index];
index--;
}
// 当index=-1时就是多减少1,所以要加1
arr[index + 1] = val;
}
}
4. 希尔排序
// 交换法
public static void shellSort1(int[] arr) {
int len = arr.length;
int temp = 0, k = len / 2;
while (k > 0) {
// 插入法
for (int i = k; i < arr.length; i++) {
for (int j = i - k; j >= 0; j -= k) {
if (arr[j] > arr[j + k]) {
temp = arr[j];
arr[j] = arr[j + k];
arr[j + k] = temp;
}
}
}
k /= 2;
}
}
// 移位法
public static void shellSort2(int[] arr) {
int len = arr.length;
int temp = 0, k = len / 2, index = 0;// 分k组
while (k > 0) {
// 对每个组进行排序
for (int i = k; i < arr.length; i++) {
temp = arr[i];// 记录arr[i]方便接下来移位
index = i - k;// 向前遍历
// 此循环用来保证 较大的向后移位
while (index >= 0 && temp < arr[index]) {
arr[index + k] = arr[index];
index -= k;//每两个元素之间相隔k个元素
}
//上面多移动了一次,所以index+1,把这组最后一个赋值给前面的
arr[index + k] = temp;
}
k /= 2;// 再进行分
}
}
5. 归并排序
// 合并数据
public static void merge(int[] arr, int left, int right, int mid, int[] temp) {
int i = left, j = mid + 1;
int k = 0, t = 0;
// 将arr数组的元素排序放到temp数组
// 左右排序合并合并
while (i <= mid && j <= right) {
if (arr[i] <= arr[j]) {
temp[k] = arr[i];
k++;
i++;
} else {
temp[k] = arr[j];
k++;
j++;
}
}
// 将没合并完成的合并
while (i <= mid) {
temp[k++] = arr[i++];
}
while (j <= right) {
temp[k++] = arr[j++];
}
// //将temp数组的元素排序放到arr数组
// 注意:是从本方法的left开始向后合并,直到本方法参数的right,并不是从arr 的头开始合并
int templeft = left;
// System.out.printf("templeft=%d, right=%d\n", templeft, right);
while (templeft <= right) {
arr[templeft] = temp[t];
templeft++;
t++;
}
// print(arr);
// print(temp);
}
// 边分离边合并
public static void mergeSort(int[] arr, int left, int right, int[] temp) {
// print(temp);
if (left < right) {
// 递归分离
int mid = (left + right) / 2;// 一分为二
mergeSort(arr, left, mid, temp);// 分离前半部分
mergeSort(arr, mid + 1, right, temp);// 分离后半部分
merge(arr, left, right, mid, temp);// 合并
} else {
return;
}
}
6. 快速排序
/**
* 快速排序
*
* @param r数组
* @param first 需要排序数组的左边索引
* @param last 需要排序的数组的右边索引
*/
public static void quickSort(int r[], int first, int last) {
int x, i, j, t;
if (first < last) {// first 必须小于last 否则会死递归
x = r[first];// 记录下第一个值
i = first;
j = last;
while (i != j) {
while (i < j && r[j] >= x)// 向前移动直到发现值大于比较值 切记i<j这循环在前
j--;
while (i < j && r[i] <= x)// 向后移动直到发现值小于比较值,当i==j必须截至,
i++;
if (i < j) {// i 绝对不能大于j,因为r[j]后面的数据都是大于x
// 交换数据
t = r[i];
r[i] = r[j];
r[j] = t;
}
}
r[first] = r[i];// 此时r[i]<=r[first],且r[i]后面的数据都大于x
r[i] = x;// 把x赋值给r[i]
quickSort(r, first, i - 1);// 继续向前递归
quickSort(r, i + 1, last);// 向后递归
}
}
7. 堆排序
public static void heapSort(int[] arr) {
// 从上朝下进行大顶堆调整,整体变成大顶堆
for (int i = arr.length / 2 - 1; i >= 0; i--) {
adjustHeap(arr, i, arr.length);
}
int temp = 0;
for (int i = arr.length - 1; i >= 0; i--) {
//将最大的数据放到树的最后一个叶节点,然后不再进行其他操作
temp = arr[0];
arr[0] = arr[i];
arr[i] = temp;
//从头开始调整。注意,由于上面的整体进行大顶堆调整,此操作只会堆根节点下面一边的子树进行调整,但是最大值不受影响
adjustHeap(arr, 0, i);
}
}
// 讲以i为非叶子节点的调整为大顶堆
// i,表示非叶子结点的序号
// len表示数组的长度
public static void adjustHeap(int[] arr, int i, int len) {
int temp = arr[i];
for (int j = i * 2 + 1; j < len; j = j * 2 + 1) {
if (j + 1 < len && arr[j] < arr[j + 1]) {//比较左右子树的大小
j++;
}
if (arr[j] > temp) {
arr[i] = arr[j];
i = j;// i指向当前结点继续朝下遍历
} else {
break;
}
}
//该结点的值赋给其替换的子树
arr[i] = temp;
}
8. 基数排序(桶排序)
public static void radixSort(int[] arr) {
int max = arr[0];
int len = arr.length;
for (int i = 0; i < len; i++) {// 找出数组中最大的数
if (arr[i] > max)
max = arr[i];
}
int maxlen = (max + "").length();// 计算最大数的数字长度
int[][] bucket = new int[len][10];// 用来表示桶
int[] bucketElements = new int[10];// 用来计算每个桶存在的数量
int index = 0;
for (int i = 0, n = 1; i < maxlen; i++, n *= 10) {
for (int j = 0; j < len; j++) {
index = arr[j] / n % 10;// 用来表示从arr[j]中取出的i位数
bucket[bucketElements[index]][index] = arr[j];// 把数放到第index号桶中第bucketElements[index]位
bucketElements[index]++;// 桶的计数器数量加一
}
int count = 0;// arr数组复制的计数器
// 将桶中的数据再复制到arr数组
for (int j = 0; j < 10; j++) {// 遍历bucketElements数组
if (bucketElements[j] != 0) {
for (int l = 0; l < len; l++) {// 遍历bucket数组
if (bucket[l][j] != 0) {
arr[count] = bucket[l][j];
count++;
}
bucket[l][j] = 0;// 结束后要置0,以供下一个循环使用
}
bucketElements[j] = 0;// 结束后要置0,以供下一个循环使用
}
}
}
}
下面我们对八大排序进行性能测试:
package sort;
import java.util.Arrays;
public class Test {
public static void main(String[] args) {
long t1, t2;
t1 = System.currentTimeMillis();
BubbleSort.bubbleSort1(randomArr(80000));
t2 = System.currentTimeMillis();
System.out.println("冒泡排序优化前耗时:" + (t2 - t1));
t1 = System.currentTimeMillis();
BubbleSort.bubbleSort2(randomArr(80000));
t2 = System.currentTimeMillis();
System.out.println("冒泡排序优化后耗时:" + (t2 - t1));
t1 = System.currentTimeMillis();
SelectSort.selectSort(randomArr(80000));
t2 = System.currentTimeMillis();
System.out.println("选择排序耗时:" + (t2 - t1));
t1 = System.currentTimeMillis();
InsertSort.insertSort(randomArr(80000));
t2 = System.currentTimeMillis();
System.out.println("插入排序耗时:" + (t2 - t1));
t1 = System.currentTimeMillis();
ShellSort.shellSort1(randomArr(80000));
t2 = System.currentTimeMillis();
System.out.println("希尔排序优化前耗时:" + (t2 - t1));
t1 = System.currentTimeMillis();
ShellSort.shellSort2(randomArr(80000));
t2 = System.currentTimeMillis();
System.out.println("希尔排序优化后耗时:" + (t2 - t1));
t1 = System.currentTimeMillis();
MergeSort.mergeSort(randomArr(80000));
t2 = System.currentTimeMillis();
System.out.println("归并排序耗时:" + (t2 - t1));
t1 = System.currentTimeMillis();
QuickSort.quickSort(randomArr(80000));
t2 = System.currentTimeMillis();
System.out.println("快速排序耗时:" + (t2 - t1));
t1 = System.currentTimeMillis();
HeapSort.heapSort(randomArr(80000));
t2 = System.currentTimeMillis();
System.out.println("堆排序耗时:" + (t2 - t1));
t1 = System.currentTimeMillis();
RadixSort.radixSort(randomArr(80000));
t2 = System.currentTimeMillis();
System.out.println("基数排序耗时:" + (t2 - t1));
}
public static void print(int[] arr) {
System.out.println(Arrays.toString(arr));
}
//生成随机数组
public static int[] randomArr(int k) {
int[] arr = new int[k];
for (int i = 0; i < k; i++) {
arr[i] = (int) (Math.random() * k * 100);
}
return arr;
}
}
由于每次用的数组数据不一样,存在优化后排序时间比优化前长的现象。