冒泡排序
原理
- 数组元素两两比较,交换位置,大元素往后放。
- 每比较一轮少一次两两比较。
public class Main{
public static void main(String[] args){
int[] arr = {24, 69, 80, 57, 13};
int temp = 0;
for(int i = 0; i < arr.length-1; i++){
// arr.length-1轮,第一轮执行arr.length-1次,每一轮减少一次
for(int j = 0; j < arr.length-1-i; j++){
if(arr[j] > arr[j+1]){
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
System.out.print(Arrays.toString(arr)); //=> [13, 24, 57, 69, 80]
}
}
选择排序
原理
- 从0开始索引,依次和后面的元素进行比较,小的元素往前放,进过一轮比较后最小的元素出现在最小索引处。
- 每比较一轮少一次两两比较。
public class Main{
public static void main(String[] args){
int[] arr = {24,69,80,57,13};
int temp = 0;
for(int i = 0; i < arr.length-1; i++){
for(int j = i+1; j < arr.length; j++){
if(arr[i] > arr[j]){
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
System.out.print(Arrays.toString(arr)); //=> [13, 24, 57, 69, 80]
}
}
直接插入排序
原理
- 从 1 索引处开始,将一个记录插入到一个长度为 m 的 有序集合 中,使之仍然保持有序。
public class Main{
public static void main (String[] args){
int[] arr = {24,69,80,57,13};
int temp = 0;
for (int i = 1; i < arr.length; i++){
for (int j = i; j > 0; j--) {
if (arr[j] < arr[j-1]) {
temp = arr[j];
arr[j] = arr[j-1];
arr[j-1] = temp;
}
}
}
System.out.println(Arrays.toString(arr));
// 转换成 while 循环
int[] arr2 = toWhile(arr);
System.out.println(Arrays.toString(arr2));
}
public static int[] toWhile(int[] arr){
int temp = 0;
// 循环次数
for (int i = 1; i < arr.length; i++) {
int j = i;
while (j>0&&arr[j] < arr[j-1]) {
temp = arr[j];
arr[j] = arr[j-1];
arr[j-1] = temp;
j--
}
}
return arr;
}
}
希尔排序
原理
实现演示:
- 先将原表按 增量 ht 分组,每个字文件按照直接插入法排序。同样,用下一个 增量 ht/2 将文件再分成子文件,再进行 直接插入法排序。直到 ht = 1 时整个文件排好序。
- 关键在于选择合适的增量,经过一轮排序后,就会然序列大致有序。然后不断缩小增量直至增量为 1。
- 基于插入排序。直接插入排序法的优化,效率更高。
- 直接插入排序其实就是增量为 1 的希尔排序。
- 使用 knuth 序列或者选择数组长度的一半作为增量。
推导过程
public class Main{
public static void main(String[] args){
int[] arr = {24,69,80,57,13};
int l = 2;
for (int i = l; i < arr.length; i++){
// 情况:步长为1,j 必须大于 0
for(int j = i; j > l-1; j -= l){
// 如果后面比前面大
if(arr[j-l] > arr[j]){
change(arr, j, j-l);
}
}
}
// 实际上就是直接插入排序 ...
l = 1;
for (int i = l; i < arr.length; i++){
// 情况:步长为1,j 必须大于 0
for(int j = i; j > l-1; j -= l){
if(arr[j-l] > arr[j]){
change(arr, j, j-l);
}
}
}
System.out.println(Arrays.toString(arr));
}
public static void change(int[] arr,int j,int t){
int temp = arr[j];
arr[j] = arr[t];
arr[t] = temp;
}
}
优化
- 希尔排序的思想就是合理的选取增量。所以就引出了 克努特序列 Knuth。
public class Main{
public static void main(String[] args){
int[] arr = {24,69,80,57,13};
for(int l = arr.length / 2; l > 0; l/=2){
for (int i = l; i < arr.length; i++){
// 情况:步长为1,j 必须大于 0
for(int j = i; j > l-1; j -= l){
if(arr[j-l] > arr[j]){
change(arr, j, j-l);
}
}
}
}
System.out.println(Arrays.toString(arr));
}
public static void change(int[] arr,int j,int t){
int temp = arr[j];
arr[j] = arr[t];
arr[t] = temp;
}
}
- 克努特序列
int h = 1;
h = h*3 - 1;
增量:1,4,13,40,121,364,...
public class Main{
public static void main(String[] args){
int[] arr = {24,69,80,57,13};
int a = 1;
while(a<=arr.length/3){
a = a*3+1;
}
for(int l = a; l > 0; l = ( l - 1 ) / 3){
for (int i = l; i < arr.length; i++){
// 情况:步长为1,j 必须大于 0
for(int j = i; j > l-1; j -= l){
if(arr[j-l] > arr[j]){
change(arr, j, j-l);
}
}
}
}
System.out.println(Arrays.toString(arr));
}
public static void change(int[] arr,int j,int t){
int temp = arr[j];
arr[j] = arr[t];
arr[t] = temp;
}
}
快速排序
原理
- 先定义一个元素作为基准数,一般会把数组中最左边的数作为基准数。
- 从两边可是检索,先从右边检索比基准数小的。再从左边检索比基准数大的,如果检索成功就交换两个元素。重复检索。
- 直至两边的检索位置重合停止检索,将该处检索位置对应的元素与基准数进行交换。检索完成后基准数归位,获得左右分区进行递归调用。
- 进行递归调用,更改基准数再进行检索。重复 1 2 3 步骤。
public class Main{
public static void main(String[] args){
// int [] arr = {6,1,2,7,9,3,4,5,10,8};
// quickSort(arr, 0, arr.length-1);
// System.out.println(Arrays.toString(arr)); //=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// 进行速度检测
int[] arr = new int[100_0000];
Random r = new Random();
for(int i = 0; i < arr.length; i++){
int num = r.nextInt();
arr[i] = num;
}
long starttime = System.currentTimeMillis();
quickSort(arr, 0, arr.length-1);
long endtime = System.currentTimeMillis();
System.out.println(endtime-starttime); //=> 205
}
public static void quickSort(int[] arr, int left, int right){
// 左边索引不能大于右边索引
if(left > right){
return;
}
// 定义基准数与最左最右索引
int base = arr[left];
int i = left;
int j = right;
// 如果i和j相遇则停止检索
while( i != j ){
// 如果右索引索引元素比基准数小停止检索
while(arr[j] >= base && j > i){
j--;
}
// 如果左索引索引元素比基准数大停止检索
while(arr[i] <= base && j > i){
i++;
}
// 停止检索跳出循环交换位置
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
// 相遇时当前位置元素与基准数进行交换
arr[left] = arr[i];
arr[i] = base;
// 递归进行重复检索,控制范围
// 基准数左边开始排,再右边
quickSort(arr, left, i-1);
quickSort(arr, j+1, right);
}
}
归并排序
原理
- 归并排序的思想就是先递归分解数组,再合并数组。
- 将数组分解最小之后,然后合并两个有序数组,基本思路是比较两个数组的最前面的数,谁小就先取谁,取了后相应的指针就往后移一位。然后再比较,直至一个数组为空,最后把另一个数组的剩余部分复制过来即可。
- 使用临时数组进行数组保存。
分析:
public class Main{
public static void main(String[] args){
int[] arr = {2,3,5,1,23,6,78,34,23,4,5,78,34,65,32,65,76,32,76,1,9};
// 拆分
sort(arr, 0, arr.length-1);
System.out.println(Arrays.toString(arr));
}
// 拆分成最小单元
public static void sort(int[] arr, int startIndex, int endIndex){
// 计算中间索引
int center = (startIndex + endIndex) / 2;
// 递归拆分,一直进行对半拆分,直到startIndex与endIndex重合,表示仅剩余一个元素。
if(startIndex < endIndex){
sort(arr, startIndex, center);
sort(arr, center+1, endIndex);
// 进行归并
merge(arr, startIndex, center, endIndex);
}
}
// 归并
public static void merge(int[] arr, int startIndex, int centerIndex, int endIndex){
// 定义一个临时数组
int[] newArr = new int[endIndex-startIndex+1];
// 定义左右两边的初始索引
int i = startIndex;
int j = centerIndex+1;
// 定义临时数组的初始索引
int k = 0;
while(i<=centerIndex&&j<=endIndex){
if(arr[i]<=arr[j]){
// 将小的元素放到新的数组中
newArr[k] = arr[i];
i++;
} else {
newArr[k] = arr[j];
j++;
}
k++;
}
// 处理剩余的元素
while(i<=centerIndex){
newArr[k] = arr[i];
i++;
k++;
}
while(j<=endIndex){
newArr[k] = arr[j];
j++;
k++;
}
// 将临时数组取到原数组中
for(int l = 0; l < newArr.length; l++){
arr[l+startIndex] = newArr[l];
}
}
}
基数排序
原理
- 不需要进行比较。
- 根据元素位数只需要对关键字进行分配和收集即可。
- 最大位数是几位就循环几次。
public class Main{
public static void main(String[] args){
int[] arr = {10,80,30,5,4,8,20};
// 定义一个二位数数组管理容器,以及统计数组
int[] counts = new int[10];
int [][] temp = new int [10][arr.length];
// 获取循环位置
int max = getMax(arr);
int len = String.valueOf(max).length();
for(int i = 0, n = 1; i < len; i++, n*=10){
// 分配
// 每个位置上的数据
for(int j = 0; j < arr.length; j++){
// 位数的值进行分配
int ws = arr[j]/n%10;
temp[ws][counts[ws]++] = arr[j];
}
// 收集
int index = 0;
// 在统计数组中获取统计数据
for(int k = 0; k < counts.length; k++){
if(counts[k]!=0){
for(int l = 0; l < counts[k]; l++){
arr[index] = temp[k][l];
index++;
}
// 清空记录
counts[k] = 0;
}
}
}
System.out.println(Arrays.toString(arr)); //=> [4, 5, 8, 10, 20, 30, 80]
}
public static int getMax(int[] arr){
int max = arr[0];
for(int i = 1; i < arr.length-1; i++){
if(max<arr[i]){
max = arr[i];
}
}
return max;
}
}
堆排序
原理
- 堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序。
- 堆分为两种:大顶堆和小顶堆,两者的差别主要在于排序方式。
- 堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。如下图:
大顶堆的存储结构为:{19,16,15,9,8,1}
小顶堆的存储结构为:{1,8,9,15,16,19}
大顶堆和小顶堆的存储结构未必是有序的,只要父节点大于他的左右孩子节点就是大顶堆了,父节点小于他的孩子左右孩子节点就是小顶堆。可以用以下的公式表示:
大顶堆:arr[i]>=arr[2i+1] && arr[i]>=arr[2i+2]
小顶堆:arr[i]<=arr[2i+1] && arr[i]<=arr[2i+2]
堆排序的基本思想和步骤
堆排序的基本思想是:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了
下面我们举例来说明堆排序的步骤。
给定序列 {15,8,1,19,16,9}
构造好的完全二叉树
根据大顶堆的原理,我们构造一个大顶堆,此时我们从最后一个非叶子节点开始,如下图。
大顶堆的存储结构为{19,16,9,8,15,1}
然后我们将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换,步骤如下。
第一步:将堆顶元素19和堆底元素1交换,然后再重建,得到新的大顶堆,存储结构为:{16,15,9,8,1,19}。
第二步:将堆顶元素16和新的无序堆的堆底元素1交换,然后再重建,得到新的大顶堆,存储结构为:{15,8,9,1,16,19}。
第三步:将堆顶元素15和新的无序堆的堆底元素1交换,然后再重建,得到新的大顶堆,存储结构为:{9,8,1,15,16,19}。
第四步:将堆顶元素9和新的无序堆的堆底元素1交换,然后再重建,得到新的大顶堆,存储结构为:{8,1,9,15,16,19}。
第五步,将堆顶元素8和新的无序堆的堆底元素1交换,交换后整个堆为有序,存储结构为:{1,8,9,15,16,19}。
public class Main{
public static void main(String[] args){
int[] arr = {9,8,7,6,5,4,3,2,1};
sort(arr);
System.out.println(Arrays.toString(arr));
}
public static void sort(int[] arr){
// 构建大顶堆,从第一个非叶子节点从下到上从左到右
for(int i = arr.length/2-1; i >= 0; i--){
// 调整结构
maxHeap(arr, i, arr.length);
}
// 反复进行首尾替换
for(int j = arr.length-1; j > 0; j--){
swap(arr, 0, j);
// 交换完成后继续进行顶堆调整
maxHeap(arr,0,j);
}
}
public static void maxHeap(int[] arr, int i, int j){
// 保存元素
int temp = arr[i];
// 遍历其子元素
for(int k= i*2+1; k < j; k = k*2+1){
// 子元素间进行比较
if(arr[k]<arr[k+1] && k+1<j){
k++;
}
if(temp<arr[k]){
arr[i] = arr[k];
i = k;
}else{
break;
}
}
// 进行交换
arr[i] = temp;
}
public static void swap(int[] arr, int start, int end){
int temp = arr[start];
arr[start] = arr[end];
arr[end] = temp;
}
}