banner
NEWS LETTER

八大排序算法梳理

Scroll down

本篇blog的语言为java实现,介意请划走

八大排序算法

  • 冒泡排序
  • 选择排序
  • 插入排序
  • 希尔排序
  • 快速排序
  • 归并排序
  • 桶排序
  • 堆排序

上述排序值得注意的是堆排序,他利用顺序二叉树实现了对堆元素的随机存取,而他所操作的虽然是数组结构,但本质上我们将其视为一个二叉树。

基本排序算法(并不复杂,甚至称不上算法,理解即可)

冒泡

1
2
3
4
5
6
7
8
9
10
11
12

public static void bubble_sort(int[] arr) {
int i, j, temp, len = arr.length;
for (i = 0; i < len - 1; i++)
for (j = 0; j < len - 1 - i; j++)
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}

时间复杂度(n^2)

每次排序将最大值放入末尾,且注意,该算法的核心在于逐步排序,内层循环将较小或者较大的值下沉。

选择排序

在冒泡排序中,我们逐步将最大或最小值后移,而选择排序优化了这个过程,比较过程并不移动数组元素,而是先找到将要移动的坐标。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/**
* Description: 选择排序
*
* @param array
* @return void
* @author JourWon
* @date 2019/7/11 23:31
*/
public static void selectionSort(int[] array) {
if (array == null || array.length <= 1) {
return;
}

int length = array.length;

for (int i = 0; i < length - 1; i++) {
// 保存最小数的索引
int minIndex = i;

for (int j = i + 1; j < length; j++) {
// 找到最小的数
if (array[j] < array[minIndex]) {
minIndex = j;
}
}

// 交换元素位置
if (i != minIndex) {
swap(array, minIndex, i);
}
}

}

/**
* Description: 交换元素位置
*
* @param array
* @param a
* @param b
* @return void
* @author JourWon
* @date 2019/7/11 17:57
*/
private static void swap(int[] array, int a, int b) {
int temp = array[a];
array[a] = array[b];
array[b] = temp;
}

插入排序

该思想和动态规划有点像,即我们假定要将插入某个元素插入一个有序数组,那么我们只需要从数组起点或者某位逐步比较即可。

注意:数组元素的比较必须满足a>=b,b>=c,一定能推出a>=c。

1
2
3
4
5
6
7
8
9
10
11
12
13
/* 插入排序 */
void insertionSort(int[] nums) {
// 外循环:已排序区间为 [0, i-1]
for (int i = 1; i < nums.length; i++) {
int base = nums[i], j = i - 1;
// 内循环:将 base 插入到已排序区间 [0, i-1] 中的正确位置
while (j >= 0 && nums[j] > base) {
nums[j + 1] = nums[j]; // 将 nums[j] 向右移动一位
j--;
}
nums[j + 1] = base; // 将 base 赋值到正确位置
}
}

较为复杂的排序

希尔排序

核心思想:通过步长指定分组,将每次分组内的数据变得有序。这样当我们减少步长时,虽然数组元素并不会因此有序,但是会变得相对容易排序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public  static  void shellsort(int[] arr){
int h=arr.length;
while (true){
h=h/2;
if(h==0){
break;
}

for (int i=h;i< arr.length;i++){//插入排序
int j=i;
int m=arr[j];
while (j-h>=0&&arr[j-h]>m){//注意这里的坐标转换,根据步长找到了上一个分组位置。
arr[j]=arr[j-h];
j-=h;
}
arr[j]=m;

}
}

快速排序

核心思想:使用基准将数据进行分组,当极限状态只剩下两个数字时,也就是完成了排序。

他和希尔排序不同的地方在于,他的分组的依据是基准,并不保证分组范围。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
    /**
* @author:wenzhuo4657
des: 错位交换处理基准数据,最终使其变成两个区间的边界
*/
public static void example(int[] arr, int left, int right) {
if (left >= right) {//越界处理
return;
}
int l = left;
int r = right;

int mid = arr[l];//1,作为基准,

// 2,错位处理,解决了基准数据移动,即便是极端情况下,也会由于while循环条件只会到达l==r,而这个位置的交换是无效的,最后再将错位的arr[l]填充
// 使基准数据也参与到排序中变动位置。
while (l < r) {

while (l < r && arr[r] >= mid) {
r--;
}
arr[l] = arr[r];
while (l < r && arr[l] < mid) {
l++;
}
arr[r] = arr[l];
}
arr[l] = mid;

example(arr, left, l-1);

example(arr, l+1, right);
}

归并排序

核心思想:递归找到两个数字的分组进行排序(其实按照递归是找到一个数字的分组,只是它的排序无效)

逐层向上将两个有序分组合并。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
    public static void example(int  []arr,int left,int right,int[] temp){

// 该函数主要用于实现递归调用的流程

if(left<right){
int min=(left+right)/2;
example(arr,left,min,temp);
example(arr,min+1,right,temp);

merge(arr,left,min,right,temp);//基于归并排序的递归流程,该方法所得到的数组均是局部有序,极限情况,为两个元素的区间,例如 [0,1]
}


}

/**
* @author:wenzhuo4657
des: 该排序生效的数组必须是局部有序的数组,或者说,[left,min]升序,[min+1,right]升序
*/
private static void merge(int[] arr, int left, int min, int right, int[] temp) {
int i=left;
int j=min+1;
int t=0;
// 1,两个有序区间[left,min] [min+1,right]
while (i<=min&&j<=right){
if (arr[i]<=arr[j]){
temp[t]=arr[i];
i++;
}else {
temp[t]=arr[j];
j++;
}
t++;
}


// 2, 基于上述while循环特点,最终只剩下一边数据没有填充,并且由于区间局部有序可知,此时省下的数据可以直接顺序填充
while (i<=min){
temp[t]=arr[i];
i++;
t++;
}
while (j<=right){
temp[t]=arr[j];
j++;
t++;
}

// 3,填充完毕,将结果复制到原本数组的区间
t=0;
for(int h=left;h<=right;h++){
arr[h]=temp[t];
t++;
}
}

桶排序

按照数字位数进行一次排序。

待补充

堆排序

利用顺序二叉树将树结构扁平化处理,实现随机存取,并且使用大顶堆或小顶堆结构获取某个范围内的最大值或者最小值。

值得注意的是该算法对于如何将一个树调整为堆结构的方式:假设某个树除了根节点,其余节点均符合这个条件,如何为这个根节点找到合适位置进行交换?这样做了之后他也一定能够变成大顶堆或者小顶堆结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
* @author:wenzhuo4657
des: 要将root下沉到合适的地方,且需要注意,以root为跟节点的这个子树(可能是0,就是这棵树本身),除了根节点以外,都符合大顶堆定义。
arr:顺序二叉树,
root: 某个节点,这里将其视为数组在该子树中找到合适位置的节点
length;排序范围
*/

public static void heap1(int arr[] ,int root,int length){
int temp=arr[root];//临时存储根节点

for (int k=root*2+1;k<length;k=k*2+1){
if (k+1<length&&arr[k]<arr[k+1]){//1,找到左右较大的那个
k++;
}
if (temp<arr[k]){//判断三元组 节点root和节点root的左右节点的最大值
arr[root]=arr[k];//这一步相当于是移位交换,交换目标为,root和k
root=k;//不断更新root节点,
}else {
break;
}
}
arr[root]=temp;//完成下沉

}

public static void heap2(int []arr){
int temp=0;
//转换顺序二叉树为大顶堆,arr.length/2-1==((arr.length-1)末尾节点-1)/2 ,从最底层开始逐渐完成大顶堆定义,换言之,他的每一个子树都符合大顶堆。
for (int i=arr.length/2-1;i>=0;i--){
heap1(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;
heap1(arr,0, j);
}
}

如图的关键方法为heap1,再传入一个未知结构的树时,他通过从最后一个非叶子节点开始逐层将子树变为堆结构(通过heap1方法),最终使整个树称为堆结构。

其他文章