八大排序算法总结之二(简单选择算法,堆排序,归并排序,基数排序)

时间:2022-12-23 09:25:00

简单选择排序 :selectSort

public static void selectSort1(int []input){
for(int i=0;i<input.length;i++){
int minloc = i;
for(int j=i+1;j<input.length;j++){
if(input[minloc]>input[j]){
minloc = j;
}
}
if(minloc!=i){
input[i] ^=input[minloc];
input[minloc] ^=input[i];
input[i] ^=input[minloc];
}
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

时间复杂度为O(n^2),算法稳定性:不稳定排序


堆排序 :heapSort

八大排序算法总结之二(简单选择算法,堆排序,归并排序,基数排序)

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;

public class HeapSort {
private static void buildMaxHeap(int[] input){
//从最后一个的父节点开始,(n-1)/2,叶子节点已经是最大堆了,所以不必调整
int startIndex=((input.length-1)-1)/2;
//从尾端开始创建最大堆,每次都是正确的堆
for(int i=startIndex;i>=0;i--){
maxHeapAdjust(input,input.length,i);
}
}
/**
*创建最大堆(从下到上)
*@param input
*@param heapSize需要创建最大堆的大小,一般在sort的时候用到,因为最多值放在末尾,末尾就不再归入最大堆了
*@param index当前需要创建最大堆的位置
*/

private static void maxHeapAdjust(int[] input,int heapSize,int index){
//当前点与左右子节点比较2n+1、2n+2
int left=2*index+1;
int right=2*index+2;
int largest=index;
if(left<heapSize&&input[index]<input[left]){
largest=left;
}
if(right<heapSize&&input[largest]<input[right]){
largest=right;
}
//得到最大值后可能需要交换,如果交换了,其子节点可能就不是最大堆了,需要重新调整
if(largest!=index){
input[largest]^=input[index];
input[index]^=input[largest];
input[largest]^=input[index];
maxHeapAdjust(input,heapSize,largest);
}
}

/**
*排序,最大值放在末尾,input虽然是最大堆,在排序后就成了递增的
*/

private static void heapSort(int[] input){
//将数组建立成为最大堆
buildMaxHeap(input);
//末尾与头交换,交换后调整最大堆
for(int i=input.length-1;i>0;i--){
int temp=input[0];
input[0]=input[i];
input[i]=temp;
maxHeapAdjust(input,i,0);
}
}
public static void main(String[] args) throws IOException {
StreamTokenizer cin = new StreamTokenizer (new BufferedReader(new InputStreamReader(System.in)));
while(cin.nextToken()!=cin.TT_EOF){
int n =(int)cin.nval;
int []input = new int[n];
for(int i=0;i<n;i++){
cin.nextToken();
input[i] = (int)cin.nval;
}
heapSort(input);
for(int i=0;i<input.length;i++){
System.out.print(input[i]+" ");
}
}
}
}
  • 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
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 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
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70

时间复杂度为O(nlogn),算法稳定性:不稳定排序


归并排序:mergeSort

归并排序主要是完成将若干个有序子序列合并成一个完整的有序子序列,是采用分治法( Divide and Conquer)的一个非常典型的应用。

设有数列{6,202,100,301,38,8,1} 
初始状态:6,202,100,301,38,8,1 
第一次归并后:{6,202},{100,301},{8,38},{1},比较次数:3; 
第二次归并后:{6,100,202,301},{1,8,38},比较次数:4; 
第三次归并后:{1,6,8,38,100,202,301},比较次数:4; 
总的比较次数为:3+4+4=11,; 
逆序数为14;

归并操作的工作原理:

  • 第一步:申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
  • 第二步:设定两个指针,最初位置分别为两个已经排序序列的起始位置;
  • 第三步:比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
  • 重复步骤3直到某一指针超出序列尾;
  • 将另一序列剩下的所有元素直接复制到合并序列尾.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;

public class MergeSort {
public static void mergeSort(int []input,int left,int right){
//分治
int mid = (left + right) / 2;
if (left < right) {
// 左边
mergeSort(input,left,mid);
// 右边
mergeSort(input,mid+1,right);
// 左右归并
merge(input,left,mid,right);
}
}
/**
* <pre>
* 二路归并:将两个有序表合并为一个有序表
* </pre>
* @param input
* @param left 左数组的第一个元素的索引
* @param center 左数组的最后一个元素的索引,center+1是右数组第一个元素的索引
* @param right 右数组最后一个元素的索引
*/

public static void merge(int [] input,int left,int center,int right){
// 临时数组
int [] tempArray = new int[right-left+1];
// 右数组第一个元素索引
int mid = center+1;
// 记录临时数组的索引
int current = 0;
//存储数组的第一个元素
int temp = left;
//从两个数组中取出最小的放入中间数组
while(left<=center && mid<=right){
if(input[left]<input[mid]){
tempArray[current]=input[left];
left++;
current++;
}else{
tempArray[current]=input[mid];
mid++;
current++;
}
}
// 剩余部分依次放入临时数组(实际上两个while只会执行其中一个)
while(left<=center){
tempArray[current]=input[left];
left++;
current++;
}
while(mid<=right){
tempArray[current]=input[mid];
mid++;
current++;
}
// 将临时数组中的内容拷贝回原数组中
current = 0;
while(temp<=right){
input[temp] = tempArray[current];
temp++;
current++;
}
}
public static void main(String[] args) throws IOException {
StreamTokenizer cin = new StreamTokenizer (new BufferedReader(new InputStreamReader(System.in)));
while(cin.nextToken()!=cin.TT_EOF){
int n =(int)cin.nval;
int []input = new int[n];
for(int i=0;i<n;i++){
cin.nextToken();
input[i] = (int)cin.nval;
}
mergeSort(input,0,1);
for(int i=0;i<input.length;i++){
System.out.print(input[i]+" ");
}
}
}
}
  • 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
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 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
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84

与快速排序和堆排序相比,归并排序的最大特点是,它是一种稳定的排序方法。时间复杂度O(nlogn)。空间复杂度O(n)。


基数排序 :radixSort(多关键字排序)

最高位优先(Most Significant Digit first)法,简称MSD法:先按k1排序分组,同一组中记录,关键码k1相等,再对各组按k2排序分成子组,之后,对后面的关键码继续这样的排序分组,直到按最次位关键码kd对各子组排序后。再将各组连接起来,便得到一个有序序列。 
最低位优先(Least Significant Digit first)法,简称LSD法:先从kd开始排序,再对kd-1进行排序,依次重复,直到对k1排序后便得到一个有序序列。

基本解法:

第一步 
以LSD为例,假设原来有一串数值如下所示: 
73, 22, 93, 43, 55, 14, 28, 65, 39, 81 
首先根据个位数的数值,在走访数值时将它们分配至编号0到9的桶子中: 

1 81 
2 22 
3 73 93 43 
4 14 
5 55 65 


8 28 
9 39 
第二步 
接下来将这些桶子中的数值重新串接起来,成为以下的数列: 
81, 22, 73, 93, 43, 14, 55, 65, 28, 39 
接着再进行一次分配,这次是根据十位数来分配: 

1 14 
2 22 28 
3 39 
4 43 
5 55 
6 65 
7 73 
8 81 
9 93 
第三步 
接下来将这些桶子中的数值重新串接起来,成为以下的数列: 
14, 22, 28, 39, 43, 55, 65, 73, 81, 93 
这时候整个数列已经排序完毕;如果排序的对象有三位数以上,则持续进行以上的动作直至最高位数为止。 
LSD的基数排序适用于位数小的数列,如果位数多的话,使用MSD的效率会比较好。MSD的方式与LSD相反,是由高位数为基底开始进行分配,但在分配之后并不马上合并回一个数组中,而是在每个“桶子”中建立“子桶”,将每个桶子中的数值按照下一数位的值分配到“子桶”中。在进行完最低位数的分配后再合并回单一的数组中。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;

public class RadixSort {

public static void radixSort(int[] number, int d) //d表示最大的数有多少位
{
int k = 0;
int n = 1;
int m = 1; //控制键值排序依据在哪一位
int[][]temp = new int[10][number.length]; //数组的第一维表示可能的余数0-9
int[]order = new int[10]; //数组orderp[i]用来表示该位是i的数的个数
while(m <= d)
{
for(int i = 0; i < number.length; i++){
int lsd = ((number[i] / n) % 10);
temp[lsd][order[lsd]] = number[i];
order[lsd]++;
}
for(int i = 0; i < 10; i++){
if(order[i] != 0)
for(int j = 0; j < order[i]; j++){
number[k] = temp[i][j];
k++;
}
order[i] = 0;
}
n *= 10;
k = 0;
m++;
}
}
public static void main(String[] args) throws IOException {
StreamTokenizer cin = new StreamTokenizer (new BufferedReader(new InputStreamReader(System.in)));
while(cin.nextToken()!=cin.TT_EOF){
int n =(int)cin.nval;
int []input = new int[n];
for(int i=0;i<n;i++){
cin.nextToken();
input[i] = (int)cin.nval;
}
radixSort(input,3);
for(int i=0;i<input.length;i++){
System.out.print(input[i]+" ");
}
}
}
}
  • 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
  • 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

设待排序列为n个记录,d个关键码,关键码的取值范围为radix,则进行链式基数排序的时间复杂度为O(d(n+radix)),基数排序法是属于稳定性的排序。