08排序算法
#
1.冒泡排序
冒泡排序(BubbleSorting)的基本思想是:通过对待排序序列从前向后(从下标较小的元素开始),依次比较相邻元素的值,若发现逆序则交换,使值较大的元素逐渐从前移向后部,就象水底下的气泡一样逐渐向上冒。
优化:因为排序的过程中,各元素不断接近自己的位置,如果一趟比较下来没有进行过交换,就说明序列有序,因此要在排序过程中设置一个标志flag判断元素是否进行过交换。从而减少不必要的比较。(这里说的优化,可以在冒泡排序写好后,在进行)
原始数组:3, 9, -1, 10, 20
第一趟排序
(1) 3, 9, -1, 10, 20 // 如果相邻的元素逆序就交换
(2) 3, -1, 9, 10, 20
(3) 3, -1, 9, 10, 20
(4) 3, -1, 9, 10, 20
第二趟排序
(1) -1, 3, 9, 10, 20 //****交换
(2) -1, 3, 9, 10, 20
(3) -1, 3, 9, 10, 20
第三趟排序
(1) -1, 3, 9, 10, 20
(2) -1, 3, 9, 10, 20
第四趟排序
(1) -1, 3, 9, 10, 20
小结冒泡排序规则
(1) 一共进行 数组的大小-1 次 大的循环
(2)每一趟排序的次数在逐渐的减少
- 优化
如果我们发现在某趟排序中,没有发生一次交换, 可以提前结束冒泡排序。这个就是优化
代码:
// 将前面额冒泡排序算法,封装成一个方法
public static void bubbleSort(int[] arr) {
// 冒泡排序 的时间复杂度 O(n^2), 自己写出
int temp = 0; // 临时变量
boolean flag = false; // 标识变量,表示是否进行过交换
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
// 如果前面的数比后面的数大,则交换
if (arr[j] > arr[j + 1]) {
flag = true;
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
//System.out.println("第" + (i + 1) + "趟排序后的数组");
//System.out.println(Arrays.toString(arr));
if (!flag) { // 在一趟排序中,一次交换都没有发生过
break;
} else {
flag = false; // 重置flag!!!, 进行下次判断
}
}
}
2.选择排序
- 算法思想
选择排序(select sorting)也是一种简单的排序方法。它的基本思想是:
第一次从 arr[0]~arr[n-1]中选取最小值, 与 arr[0]交换, 第二次从 arr[1]~arr[n-1]中选取最小值, 与 arr[1]交换, 第三次从 arr[2]~arr[n-1]中选取最小值, 与 arr[2] 交换,…,第 i 次从 arr[i-1]~arr[n-1]中选取最小值,与 arr[i-1]交换,…, 第 n-1 次从 arr[n-2]~arr[n-1]中选取最小值, 与 arr[n-2]交换,总共通过 n-1 次,得到一个按排序码从小到大排列的有序序列。

原始的数组 : 101, 34, 119, 1
第一轮排序 : 1, 34, 119, 101
第二轮排序 : 1, 34, 119, 101
第三轮排序 : 1, 34, 101, 119
说明:
\1. 选择排序一共有 数组大小 - 1 轮排序
\2. 每1轮排序,又是一个循环, 循环的规则(代码)
2.1先假定当前这个数是最小数
2.2 然后和后面的每个数进行比较,如果发现有比当前数更小的数,就重新确定最小数,并得到下标
2.3 当遍历到数组的最后时,就得到本轮最小数和下标
2.4 交换
代码:
public static void selectsort(int []arr){
for(int i=0;i<arr.length;i++){
int index = i;
int max=arr[i];
for(int j=i+1;j<arr.length;j++){
if(max<=arr[j]){
max=arr[j];
index=j;
}
}
if(index!=i) {
arr[index] = arr[i];
arr[i] = max;
}
}
}
public static void show(int []arr){
for(int i=0;i<arr.length;i++){
System.out.printf("%d"+" ",arr[i]);
}
System.out.println();
}
3.插入排序
算法思想:
插入排序(InsertionSorting)的基本思想是:把n个待排序的元素看成为一个有序表和一个无序表,开始时有序表中只包含一个元素,无序表中包含有n-1个元素,排序过程中每次从无序表中取出第一个元素,把它的排序码依次与有序表元素的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表。

代码:
public static void insertsort(int[] arr) {
int insertval = 0;
int insertindex = 0;
for (int i = 1; i < arr.length; i++) {
insertval = arr[i];
insertindex = i - 1;
// 给insertVal 找到插入的位置
// 说明
// 1. insertIndex >= 0 保证在给insertVal 找插入位置,不越界
// 2. insertVal < arr[insertIndex] 待插入的数,还没有找到插入位置
// 3. 就需要将 arr[insertIndex] 后移
while (insertindex >= 0 && insertval < arr[insertindex]) {
arr[insertindex + 1] = arr[insertindex];
insertindex--;
}
// 当退出while循环时,说明插入的位置找到, insertIndex + 1
//这里我们判断是否需要赋值
if (insertindex + 1 != i) {
arr[insertindex + 1] = insertval;
}
}
}
4.希尔排序
我们看简单的插入排序可能存在的问题. 数组 arr = {2,3,4,5,6,1}
这时需要插入的数 1( 最小), 这样的过程是:
{2,3,4,5,6,6}
{2,3,4,5,5,6}
{2,3,4,4,5,6}
{2,3,3,4,5,6}
{2,2,3,4,5,6}
{1,2,3,4,5,6}
结论: 当 需要插入的数是较小的数时, 后移的次数明显增多,对 效率有影响.
算法思想:
希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。

希尔排序时,对有序序列在插入时采用交换法,并测试排序速度;
交换法代码(速度慢):
public static void shellsort(int []arr){
int temp=0;
// 根据前面的逐步分析,使用循环处理
for(int g=arr.length/2;g>0;g/=2){
for(int i = g;i<arr.length;i++){
// 遍历各组中所有的元素(共gap组,每组有个元素), 步长gap
for(int j=i-g;j>=0;j-=g){
// 如果当前元素大于加上步长后的那个元素,说明交换
if(arr[j]>arr[j+1]){
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
}
插入法(较快):
public static void shellsort2(int []arr){
// 增量gap, 并逐步的缩小增量
for(int g=arr.length/2;g>0;g/=2){
// 从第gap个元素,逐个对其所在的组进行直接插入排序
for(int i = g;i<arr.length;i++){
int temp =arr[i];
int index = i-g;
while(index>=0&&temp<arr[index]){
//移动
arr[index+g]=arr[index];
index-=g;
}
// 当退出while循环时,说明插入的位置找到, insertIndex + 1
//这里我们判断是否需要赋值
//当退出while后,就给temp找到插入的位置
if (index + g != i) {
arr[index + g] = temp;
}
}
}
}
5.快速排序
快速排序(Quicksort)是对冒泡排序的一种改进。基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
//以中间的数为基准
public static void quicksort(int[] arr, int left, int right) {
int l = left;//左下标
int r = right;//右下标
//pivot中轴值
int pivot = arr[(left + right) / 2];
int temp = 0;//临时变量,作为交换时使用
//while循环的目的是让比pivot值小的放到左边
//比pivot大的放在右边
while (l < r) {
//在pivot的左边一直找,找到大于等于pivot值,才推出
while (arr[l] < pivot) {
l += 1;
}
//在pivot的右边一直找,找到小于等于pivot值才退出
while (arr[r] > pivot) {
r -= 1;
}
//如果l>=r说明pivot的左右两的值,已经按照左边全是
//小于等于pivot的值,右边全是大于等于pivot的值
if (l >= r) {
break;
}
//交换
temp = arr[l];
arr[l] = arr[r];
arr[r] = temp;
//如果交换完成后,发现这个arr[l]==pivot值相等 则r--,前移
if (arr[l] == pivot) {
r -= 1;
}
//如果交换完成后,发现arr[r]==pivot值相等 则l++,后移
if (arr[r] == pivot) {
l += 1;
}
}
//如果l==r,必须l++,r--,否则出现栈溢出
if (l == r) {
l += 1;
r -= 1;
}
//向左递归
if (left < r) {
quicksort(arr, left, r);
}
//向右递归
if (right > l) {
quicksort(arr, l, right);
}
}
//以第一个数为基准
public static void quicksort2(int[] arr, int left, int right) {
if (left > right) {
return;
}
int basevalue = arr[left];
int i = left, j = right;//左指针
while (i != j) {
//右指针从右至左遍历,直至遇见比基准值小的数
while (arr[j] >= basevalue && i < j) {
j--;
}
//左指针从第一个遍历,直至遇到
while (arr[i] <= basevalue && i < j) {
i++;
}
if (i < j) {//l与r没有相遇
//交换位置
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
//此时l与r相遇与基准值交换顺序
arr[left] = arr[i];
arr[i] = basevalue;
quicksort2(arr, left, i - 1);
quicksort2(arr, i + 1, right);
}
6.归并排序
归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。

再来看看治阶段,我们需要将两个已经有序的子序列合并成一个有序序列,比如上图中的最后一次合并,要将[4,5,7,8]和[1,2,3,6]两个已经有序的子序列,合并为最终序列[1,2,3,4,5,6,7,8],来看下实现步骤
package Algorithm.Sort;
import java.util.Arrays;
public class MergetSort {
public static void main(String[] args) {
int arr[] = {8, 4, 5, 7, 1, 3, 6, 2};
int[] temp = new int[arr.length];
mergetsort(arr, 0, arr.length - 1, temp);
System.out.println(Arrays.toString(arr));
}
//分+合方法
public static void mergetsort(int[] arr, int left, int right, int[] temp) {
if (left < right) {
int mid = (left + right) / 2; //中间索引
//向左递归进行分解
mergetsort(arr, left, mid, temp);
//向右递归进行分解
mergetsort(arr, mid + 1, right, temp);
//合并
merge(arr, left, mid, right, temp);
}
}
//合并的方法
/**
* @param arr 排序的原始数组
* @param left 左边有序序列的初始索引
* @param mid 中间索引
* @param right 右边索引
* @param temp 做中转的数组
*/
public static void merge(int[] arr, int left, int mid, int right, int[] temp) {
int i = left;//初始化i, 左边有序序列的初始索引
int j = mid+1;//初始化j, 右边有序序列的初始索引
int t = 0;// 指向temp数组的当前索引
//(一)
//先把左右两边(有序)的数据按照规则填充到temp数组
//直到左右两边的有序序列,有一边处理完毕为止
while (i <= mid && j <= right) {
//如果左边的有序序列的当前元素,小于等于右边有序序列的当前元素
//即将左边的当前元素,填充到 temp数组
//然后 t++, i++
if (arr[i] <= arr[j]) {
temp[t] = arr[i];
i++;
t++;
} else {//反之,将右边有序序列的当前元素,填充到temp数组
temp[t] = arr[j];
j++;
t++;
}
}
//(二)
//把有剩余数据的一边的数据依次全部填充到temp
while (i <= mid) {//左边有剩余
temp[t] = arr[i];
t++;
i++;
}
while (j <= right) {//右边有剩余
temp[t] = arr[j];
t++;
j++;
}
//(三)
//将temp数组的元素拷贝到arr
//注意,并不是每次都拷贝所有
t = 0;
int templeft = left;
//第一次合并 tempLeft = 0 , right = 1 // tempLeft = 2 right = 3 // tL=0 ri=3
//最后一次 tempLeft = 0 right = 7
while (templeft <= right) {
arr[templeft] = temp[t];
t++;
templeft++;
}
}
}
7. 基数排序
基本思想:将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。
将数组 {53, 3, 542, 748, 14, 214} 使用基数排序, 进行升序排序
第一轮排序:

第二轮排序:

第三轮排序:

代码:
public static void radixsort(int []arr){ //根据前面的推导过程,我们可以得到最终的基数排序代码 //1. 得到数组中最大的数的位数 int max = arr[0]; //假设第一数就是最大数 for(int i = 1; i < arr.length; i++) { if (arr[i] > max) { max = arr[i]; } } //得到最大数是几位数 int maxLength = (max + "").length(); //定义一个二维数组,表示10个桶, 每个桶就是一个一维数组 //说明 //1. 二维数组包含10个一维数组 //2. 为了防止在放入数的时候,数据溢出,则每个一维数组(桶),大小定为arr.length //3. 名明确,基数排序是使用空间换时间的经典算法 int[][] bucket = new int[10][arr.length]; //为了记录每个桶中,实际存放了多少个数据,我们定义一个一维数组来记录各个桶的每次放入的数据个数 //可以这里理解 //比如:bucketElementCounts[0] , 记录的就是 bucket[0] 桶的放入数据个数 int[] bucketElementCounts = new int[10]; //这里我们使用循环将代码处理 for(int i = 0 , n = 1; i < maxLength; i++, n *= 10) { //(针对每个元素的对应位进行排序处理), 第一次是个位,第二次是十位,第三次是百位.. for(int j = 0; j < arr.length; j++) { //取出每个元素的对应位的值 int digitOfElement = arr[j] / n % 10; //放入到对应的桶中 bucket[digitOfElement[bucketElementCounts[digitOfElement]]= arr[j]; bucketElementCounts[digitOfElement]++; } //按照这个桶的顺序(一维数组的下标依次取出数据,放入原来数组) int index = 0; //遍历每一桶,并将桶中是数据,放入到原数组 for(int k = 0; k < bucketElementCounts.length; k++) { //如果桶中,有数据,我们才放入到原数组 if(bucketElementCounts[k] != 0) { //循环该桶即第k个桶(即第k个一维数组), 放入 for(int l = 0; l < bucketElementCounts[k]; l++) { //取出元素放入到arr arr[index++] = bucket[k][l]; } } //第i+1轮处理后,需要将每个 bucketElementCounts[k] = 0 !!!! bucketElementCounts[k] = 0; } }
8. 堆排序
堆排序基本介绍
-
堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序。
-
堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆, 注意 : 没有要求结点的左孩子的值和右孩子的值的大小关系。
-
每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆
-
大顶堆举例说明

我们对堆中的结点按层进行编号,映射到数组中就是下面这个样子:

大顶堆特点:arr[i] >= arr[2i+1] &&arr[i] >=arr[2i+2] // i 对应第几个节点,i从0开始编号.
- 小顶堆举例说明

小顶堆:arr[i]<=arr[2i+1]&&arr[i]<=arr[2i+2]//i对应第几个节点,i从0开始编号
堆排序基本思想
-
将待排序序列构造成一个大顶堆
-
此时,整个序列的最大值就是堆顶的根节点。
-
将其与末尾元素进行交换,此时末尾就为最大值。
-
然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。
可以看到在构建大顶堆的过程中,元素的个数逐渐减少,最后就得到一个有序序列了.
要求:给你一个数组 {4,6,8,5,9} , 要求使用堆排序法,将数组升序排序。
步骤一 构造初始堆。将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)。
- 假设给定无序序列结构如下

-
此时我们从最后一个非叶子结点开始(叶结点自然不用调整,第一个非叶子结点 arr.length/2-1=5/2-1=1,也就是下面的6结点),从左至右,从下至上进行调整。
![clip_image004]()
-
找到第二个非叶节点4,由于[4,9,8]中9元素最大,4和9交换。

-
这时,交换导致了子根[4,5,6]结构混乱,继续调整,[4,5,6]中6最大,交换4和6。
-
此时,我们就将一个无序序列构造成了一个大顶堆。
![clip_image008]()
步骤二 将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换。
- 将堆顶元素9和末尾元素4进行交换

-
重新调整结构,使其继续满足堆定义
![clip_image012]()
-
进行交换,得到第二大元素8.

- 后续过程,继续进行调整,交换,如此反复进行,最终使得整个序列有序

再简单总结下堆排序的基本思路:
1. 将无序序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;
2. 将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;
3. 重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。
//将一个数组(二叉树), 调整成一个大顶堆 public static void adjustHeap(int arr[], int i, int len){ int temp = arr[i];//先取出当前元素的值,保存在临时变量 //开始调整 //说明 //1. k = i * 2 + 1 k 是 i结点的左子结点 for(int k=2*i+1;k<len;k=2*k+1){ if(k+1<len&&arr[k]<arr[k+1]){ k++;//比较父节点i对应的两个左右孩子节点大小 } if(arr[i]<arr[k]){//如果子结点大于父结点 arr[i]=arr[k];//把较大的值赋给当前结点 i=k;//!!! i 指向 k,继续循环比较 } } //当for 循环结束后,我们已经将以i 为父结点的树的最大值,放在了 最顶(局部) arr[i]=temp;//将temp值放到调整后的位置 }
代码解析:





浙公网安备 33010602011771号