目录
1.简介:
2.性能:
二.具体实现
1.冒泡排序
2.简单选择排序
3.直接插入排序
4.希尔排序(对直接插入的改进)
5.快速排序
6.归并排序
7.基数排序(桶排序的扩展)
8.堆排序
一.排序算法的简单介绍
3.总结和对比
每次遍历排序都找出一个最大值放在后面 就像冒泡一样 应用了交换的思想
[3, 9, -1, 10, 20]
第1次遍历排序:
[3, -1, 9, 10, 20]
第2次遍历排序:
[-1, 3, 9, 10, 20]
第3次遍历排序:
[-1, 3, 9, 10, 20]
第4次遍历排序:
[-1, 3, 9, 10, 20]
最终排序结果:
[-1, 3, 9, 10, 20]
*所以5个数组进行4次遍历排序就可
*根据上面的遍历我们还发现第三次遍历数组已经有序,无需进行第四次遍历,所以我们可以对这点进行优化 。 也就是如果经历一次遍历排序一次交换也没发生,那么我们就认为这个数组已经有序,直接retuen.
1.1代码实现:
/**
* 冒泡排序
* @param arr 进行排序的数组
* @return 排好序的数组
*/
public static int[] bubbleSort(int[] arr) {
int temp = 0;
int count = 0;
for (int i = arr.length-1; i > 0; i--) {
count = 0;
//每次循环都遍历出了一个最大的放在数组的后面
for (int j = 0; j < i ; j++) {
//如果比后一个元素大则进行交换
if(arr[j] > arr[j+1]) {
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
//记录进行交换的次数
count++;
}
}
// System.out.println("第" + (arr.length-i) +"次排序:");
// System.out.println(Arrays.toString(arr));
//说明已经有序了 无须在排
if (count == 0) {
return arr;
}
}
return arr;
}
1.2测试排序十万个随机数所需要的时间:
//创建随机数组
int[] testArr = new int[100000];
for (int i = 0; i < testArr.length; i++) {
//生成[0,8000]间的随机数
testArr[i] = (int) (Math.random()*8000000);
}
//开始时间
Date dataBegin = new Date();
//冒泡排序
sort.bubbleSort(testArr);
//结束时间
Date dataEnd = new Date();
System.out.println(dataEnd.getTime()-dataBegin.getTime());
结果:14523ms
每次遍历排序都选择一个最小值 然后与最前面的数进行交换 与冒泡排序不同 只交换一次
[3, 9, -1, 10, 20]
第1次遍历排序:
[-1, 9, 3, 10, 20]
第2次遍历排序:
[-1, 3, 9, 10, 20]
第3次遍历排序:
[-1, 3, 9, 10, 20]
第4次遍历排序:
[-1, 3, 9, 10, 20]
最终排序结果:
[-1, 3, 9, 10, 20]
蓝色代表每次遍历要找到的最小值
2.1代码实现
/**
* 选择排序
* @param arr 进行排序的数组
* @return 排好序的数组
*/
public static int[] selectSort(int[] arr) {
int temp = 0;
//最小值的下标
int min = 0;
for (int i = 0; i < arr.length - 1; i++) {
//先假设最小的数是arr[i]
min = i;
//遍历找到最小的那个
for (int j = i + 1; j < arr.length; j++) {
//如果发现arr[j] 比 arr[min]小 则令min = j
if(arr[j] < arr[min]) {
min = j;
}
}
//将的到的最小值与arr[i]进行交换
if (min != i) {
temp = arr[min];
arr[min] = arr[i];
arr[i] = temp;
}
// System.out.println("第" + (i+1) +"次排序:");
// System.out.println(Arrays.toString(arr));
}
return arr;
}
2.2测试排序十万个随机数所需要的时间:
//创建随机数组
int[] testArr = new int[100000];
for (int i = 0; i < testArr.length; i++) {
//生成[0,8000]间的随机数
testArr[i] = (int) (Math.random()*8000000);
}
//开始时间
Date dataBegin = new Date();
//选择排序
sort.selectSort(testArr);
//结束时间
Date dataEnd = new Date();
System.out.println(dataEnd.getTime()-dataBegin.getTime());
结果:4024ms
先将要排序数组中第一个数当作以已排序好的数组,然后依次遍历剩下的数 按顺序插入已排序好的数组。全部遍历一遍后则得到一个排序好的数组。
[3, 9, -1, 10, 20]
第1次遍历排序:
[3, 9, -1, 10, 20]
第2次遍历排序:
[-1, 3, 9, 10, 20]
第3次遍历排序:
[-1, 3, 9, 10, 20]
第4次遍历排序:
[-1, 3, 9, 10, 20]
最终排序结果:
[-1, 3, 9, 10, 20]
蓝色对应代码中的insertValue
红色代表已排好序的数组
3.1代码实现:
/**
* 插入排序
* @param arr 进行排序的数组
* @return 排好序的数组
*/
public static int[] insertSort(int[] arr) {
//要插入的值
int insertValue;
//插入的下标
int insertIndex;
for (int i = 1; i < arr.length; i++) {
insertValue = arr[i];
//先从已经排好序的数组中最后一个数开始比较
insertIndex = i - 1;
//如果下标小于0了说明已经跟 已排好序的数组全部做过了比较
//如果要插入的那个值 小于 已排好序的数组中最后一个 九将这个数后移以为 然后在往前遍历 依此比较
//知道找到一个比要插入值小的 则该点就是要插入的点
//如果一次while循环也没进入 则说明要插入的这个值比 已排好序的数组中所有的都要大
while (insertIndex >= 0 && insertValue < arr[insertIndex]) {
arr[insertIndex + 1] = arr[insertIndex];
insertIndex--;
}
//将值插入
arr[insertIndex + 1] = insertValue;
System.out.println("第" + i +"次遍历排序:");
System.out.println(Arrays.toString(arr));
}
return arr;
}
3.2测试排序十万个随机数所需要的时间:912ms
希尔排序是一种高效的排序算法,它基于插入排序算法。如果较小的值在最右边并且必须移到最左边,在这样的情况下入过用直接插入排序代价很大,采用希尔排序就能很好的解决这个问题。希尔排序也称缩小排量排序。
arr = [8, 9, 1, 7, 2, 3, 5, 4, 6, 0]
group = arr.lenght()/2
1)先将数组分成数group 个组 然后把每组中数据进行排序;
2)然后再令group = group/2 ,再对每组中数据进行排列;
3)重复上面步骤,知道最后分成 group = 1时,再对每组中数据进行排列,这时数组就是有序数组了。
[8, 9, 1, 7, 2, 3, 5, 4, 6, 0]
第1次遍历排序:分成5组进行排序
[3, 5, 1, 6, 0, 8, 9, 4, 7, 2]
第2次遍历排序:分成2组进行排序
[0, 2, 1, 4, 3, 5, 7, 6, 9, 8]
第3次遍历排序:分成1组进行排序
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
最终排序结果:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
4.1.1代码实现(交换法)
/**
* 希尔排序
* @param arr 进行排序的数组
* @return 排好序的数组
*/
public static int[] shellSort(int[] arr) {
int temp = 0;
//分成几个一组
int group = arr.length / 2;
while (group != 0) {
for (int i = group; i < arr.length; i++) {
//遍历每组中的元素,步长为group
for (int j = i - group; j >= 0; j -= group) {
//采用交换法
if (arr[j] > arr[j + group]) {
temp = arr[j];
arr[j] = arr[j + group];
arr[j + group] = temp;
}
}
}
// System.out.println("第" + (++count) +"次遍历排序:分成" + group +"组");
// System.out.println(Arrays.toString(arr));
//进行下一次分组
group = group / 2;
}
return arr;
}
4.1.2测试排序十万个随机数所需要的时间: 7039ms
4.2.1代码实现(移位法)
/**
* 希尔排序 (移位法) 效率高
* @param arr 进行排序的数组
* @return 排好序的数组
*/
public static int[] shellSort(int[] arr) {
int temp = 0;
//分成几个一组
int group = arr.length / 2;
while (group != 0) {
for (int i = group; i < arr.length; i++) {
//j代表待插入值的下标
int j = i;
temp = arr[j];
if (arr[j] < arr[j - group]) {
//与直接插入的思想相似 找到比它小的 就让他后移group位
while (j - group >= 0 && temp < arr[j - group]) {
arr[j] = arr[j - group];
j -= group;
}
//退出while时 代表找到temp位置
arr[j] = temp;
}
}
//进行下一次分组
group = group / 2;
}
return arr;
}
4.2.2测试排序十万个随机数所需要的时间: 21ms
*所以移位法的性能远高于交换法
快速排序(Quicksort)是对冒泡排序的一种改进。基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数掘都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
缺点:不稳定
举例:
1 4 7 5 3 6 4 0
取中间值 (0+7)/2=3 所以也就是5
左边索引从0开始遍历 右边索引从7开始遍历
1 4 7 5 3 6 4 0
先遍历左边找到比5大的
1 4 7 5 3 6 4 0
然后再遍历右边找到个比5小的
1 4 7 5 3 6 4 0 ==》1 4 0 5 3 6 4 0
重复上述步骤
1 4 0 5 3 6 4 7 ==》1 4 0 4 3 6 5 7
1 4 0 4 3 6 5 7 ==》1 4 0 4 3 5 6 7
这就完成了第一次分割 1 4 0 4 3 5 6 7
然后继续左边1 4 0 4 3的分割 右边 6 7分割
5.1代码实现
public class QuickSort {
public static void quickSort(int[] arr) {
quickSort(arr, 0, arr.length - 1);
}
public static void quickSort(int[] arr, int star, int end) {
if (star >= end) {
return ;
}
int l = star;
int r = end;
int piovt = arr[(l + r)/2];
while (l <= r) {
while (l <= r && arr[l] < piovt) {
l++;
}
while (l <= r && arr[r] > piovt) {
r--;
}
//交换
if (l <= r) {
int temp = arr[l];
arr[l] = arr[r];
arr[r] = temp;
l++;
r--;
}
}
quickSort(arr,star,r);
quickSort(arr,l,end);
}
}
5.2测试排序十万个随机数所需要的时间: 16ms
归并排序是利用归并的思想实现的排序方法,该算法采用经典的分治策略(分治法将问题分成一些小的问题然后递归求解,而治的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。
缺点:需要额外的空间
6.1.1代码实现
public class MergeSort {
public static void mergeSort(int[] arr) {
int[] temp = new int[arr.length];
mergeSort(arr,0, arr.length-1, temp);
}
public static void mergeSort(int[] arr, int star, int end, int[] temp) {
if (star >= end) {
return;
}
int mid = star + (end - star) / 2;
//分
mergeSort(arr, star, mid, temp);
mergeSort(arr, mid + 1, end, temp);
//治
merge(arr,star,mid,end,temp);
}
private static void merge(int[] arr, int star, int mid, int end, int[] temp) {
int left = star;
int right = mid + 1;
int index = star;
while (left <= mid && right <= end) {
if (arr[left] < arr[right]) {
temp[index++] = arr[left++];
} else {
temp[index++] = arr[right++];
}
}
while (left <= mid) {
temp[index++] = arr[left++];
}
while (right <= end) {
temp[index++] = arr[right++];
}
for (int i = 0; i <= end; i++) {
arr[i] = temp[i];
}
}
}
6.1.2测试排序十万个随机数所需要的时间: 2244ms
经过测试我发先 把临时数组当成参数时 可以大大提高该方法的效率
原因可能是每次归并都需要创建一个新数组 所以花费了很多时间
6.2.1代码改进
/**
* 归并排序
* @param arr 待排序数组
* @param leftIndex 最左侧下标
* @param rightIndex 最右侧下标
* @param tempArr 临时数组
* @return
*/
public static int[] mergeSort(int[] arr,int leftIndex, int rightIndex, int[] tempArr) {
//先分 通过递归将数组分成单个 再归并
if (leftIndex < rightIndex) {
//中间索引 也就是左侧最后一个数的索引
int midIndex = (leftIndex + rightIndex)/2;
//向左递归
mergeSort(arr, leftIndex, midIndex, tempArr);
//向右递归
mergeSort(arr, midIndex + 1, rightIndex, tempArr);
//全部分好了之后 进行合并
merge(arr,leftIndex,rightIndex,tempArr);
}
return arr;
}
/**
* 将已经分好的 进行归并
* @param arr 待归并数组
* @param leftIndex 左边第一个数的索引
* @param rightIndex 右侧最后一个数的索引
* @param tempArr 临时数组
*/
public static void merge(int[] arr, int leftIndex, int rightIndex, int[] tempArr) {
//中间索引 也就是左侧最后一个数的索引
int midIndex = (leftIndex + rightIndex)/2;
//左边第一个数的索引
int l = leftIndex;
//右侧第一个数的索引
int r = midIndex + 1;
int tempIndex = 0;
//当有一侧都遍历完成时退出while
while (l <= midIndex && r <= rightIndex) {
if (arr[l] <= arr[r]) {
tempArr[tempIndex] = arr[l];
tempIndex++;
l++;
} else {
tempArr[tempIndex] = arr[r];
tempIndex++;
r++;
}
}
//说明左面没遍历完 仍须遍历
while (l <= midIndex) {
tempArr[tempIndex] = arr[l];
tempIndex++;
l++;
}
//说明右面没遍历完 仍须遍历
while (r <= rightIndex) {
tempArr[tempIndex] = arr[r];
tempIndex++;
r++;
}
//都遍历完成后 把tempArr赋给arr
//注意 这时并不是copy所有数据 它可能只时归并中的一个小步骤
tempIndex = 0;
l = leftIndex;
// System.out.println("左索引"+l+"右索引" +rightIndex);
// System.out.println("要归并的数组" + Arrays.toString(tempArr));
while (l <= rightIndex) {
arr[l] = tempArr[tempIndex];
l++;
tempIndex++;
}
}
6.2.2测试排序十万个随机数所需要的时间: 13ms
基数排序基本思想:将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。
先创建10个和待排序数组一样大的桶,把待排序数组一次遍历,按照个位数字放在对应的 0 1 2 3 4 5 6 7 8 9的10个桶里,放完后在从0~9号桶按顺序在放回数组,然后再按照十位数.....只到遍历完数组最大值的位数(比如待排序数组最大值只有三位 这是我们只需要遍历到百位就好)。数列就变成一个有序序列。
要注意的是基数排序非常消耗内存 因为他会创建10个跟待排序数组一样大的桶;
7.1代码实现
/**
* 基数排序
* @param arr 待排序数组
* @return 排好序的数组
*/
public static int[] radixSort(int[] arr) {
int max = arr[0];
//遍历出数组的最大值
for (int t = 0; t < arr.length; t++) {
if (max < arr[t]) {
max = arr[t];
}
}
//最大位数
int digit = (max+"").length();
//创建一个二维数组 模拟10个桶 分别装 0 1 2 3 4 5 6 7 8 9
int[][] bucket = new int[10][arr.length];
//用来记录桶中存放了几个数据 比如bucketElementCounts[2] = 3; 就代表存放2的那个桶中有3个数据
int[] bucketElementCounts = new int[10];
int temp;
//个 十 百 位....只到遍历到最高位
for (int w = 0; w < digit; w++) {
//放进对应的桶中
for (int i = 0; i < arr.length; i++) {
//获取对应的位的值 比如第一次就是取个位的值 然后再取十位的值 以此类推
temp = (int) (arr[i] / ( Math.pow(10,w)) % 10);
//放进对应的桶中
bucket[temp][bucketElementCounts[temp]] = arr[i];
//记录桶中存放元素的个数++
bucketElementCounts[temp]++;
}
//当做遍历arr的索引
int index = 0;
//从桶中依次放进arr中 j代表哪个桶
for (int j = 0; j < 10; j++) {
//如果桶中有数据在遍历
if(bucketElementCounts[j] != 0) {
for (int k = 0; k < bucketElementCounts[j]; k++) {
//取出元素放在arr中
arr[index] = bucket[j][k];
index++;
}
}
//遍历完一个桶后 要把记录桶中数据的个数清零 要不然会影响下一次的存放
bucketElementCounts[j] = 0;
}
}
return arr;
}
7.2测试排序一百万个随机数所需要的时间: 526ms
所以它在处理大量数据时效率非常高
本代码没有考虑负数问题 后面补充
一般升序采用大顶堆,降序采用小顶堆
大顶堆:父节点比子节点都要大
小顶堆:父节点比子节点都要小
8.1堆排序基本思想
1) 将待排序序列构造成一个大顶堆
2) 此时,整个序列的最大值就是堆顶的根节点.
3) 将其与末尾元素进行交换,此时末尾就为最大值。
4) 然后将剩余 n-1 个元素重新构造成一个堆,这样会得到 n 个元素的次小值。如此反复执行,能得到一个有序序列了。
8.2过程分析
假定待排序数组arr = { 4, 6, 8, 5, 9} 要求将数组升序排序。
2) .此时我们从最后一个非叶子结点开始(叶结点自然不用调整,第一个非叶子结点
arr.length/2-1=5/2-1=1,也就是下面的 6 结点),从左至右,从下至上进行调整。
4) 这时,交换导致了子根[4,5,6]结构混乱,继续调整,[4,5,6]中 6 最大,交换 4 和 6。
此时,我们就将一个无序序列构造成了一个大顶堆。
4) 后续过程,继续进行调整,交换,如此反复进行,最终使得整个序列有序
/**
* 堆排序
* @param arr 待排序数组
* @return 有序数组
*/
public static int[] heapSort(int[] arr) {
int temp = 0;
//把无序数组 变成大顶堆
for (int i = arr.length/2 -1; i >= 0; i--) {
max2Root(arr, i, arr.length);
}
// System.out.println(Arrays.toString(arr));
//将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;
//重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。
for (int j = arr.length - 1; j > 0; j--) {
//交换
temp = arr[j];
arr[j] = arr[0];
arr[0] = temp;
//把剩下数组继续变成大顶堆
max2Root(arr, 0, j);
}
return arr;
}
/**
* 将数组变成大顶堆
* @param arr 待排序数组
* @param i 父节点下标
* @param lenght 数组长度
*/
public static void max2Root(int[] arr, int i, int lenght) {
int temp = arr[i];
//将最大值与父节点交换
for (int k = i * 2 + 1; k < lenght; k = k * 2 + 1) {
//如果左子节点大于右子节点 则k指向右子节点
if (k + 1 < lenght && arr[k] < arr[k + 1]) {
k = k + 1;
}
//如果父节点 小于 最大的子节点
if (temp < arr[k]) {
arr[i] = arr[k];
//让i指向k 继续循环
i = k;
} else {
//父节点就是最大的了 直接退出
break;
}
}
//将 temp放到调整后的位置
arr[i] = temp;
}
8.5测试排序十万个随机数所需要的时间: 14ms