线形排序 java_常用排序算法及Java实现

排序算法,可以分为内部排序和外部排序两大种。这篇文章主要对内部排序进行介绍。内部排序又分为两类,基于比较的非线性时间类,和非比较的线性时间类。前一类又可以分为四种,交换排序(包括冒泡排序和快速排序),插入排序(包括简单插入排序和希尔排序),选择排序(包括简单选择排序和堆排序)以及归并排序;后者主要包含三种,计数排序,桶排序和基数排序。

总体来说,快排、堆排和归并排序是非线性时间中最快的三种。一般认为,快排的时间效率会比堆排更好。另一方面,快排和堆排都是不稳定的算法,只有归并排序、冒泡排序和插入排序是稳定的算法,因此Java中的sort算法在实现的时候,有一个阈值,大概是60左右。数组长度低于这个阈值的,直接使用插入排序;高于这个阈值的,如果元素是基本数据类型,救使用快排,如果是引用数据类型,就使用归并排序。而对于基于比较的排序算法,都很依赖于数据本身的分布——在数据均匀分布的情况下,效率可以很高。计数排序是用来排序0到100之间的数字的最好的算法,但是它不适合按字母顺序排序人名;计数排序则要求对每一位使用的排序算法都是稳定的。

下面我们对这九种算法进行详细的介绍。为了方便,我们设定所有的排序都基于整形数组,需要按照从小到大的顺序进行排列。在这些排序算法中,有一些公用的操作,比如元素交换,元素打印,寻找最大值最小值等,为了代码的复用,我们定义了工具类Tools,首先给出Tools类的代码。

1 public class Tools {2 public static void swap(int[] arr, int i, int j) {3 int tmp =arr[i];4 arr[i] =arr[j];5 arr[j] =tmp;6 }7

8 public static void printArray(int[] arr) {9 int i = 0;10 System.out.print("[");11 for (; i < arr.length - 1; i++) {12 System.out.print(arr[i] + ",");13 }14 System.out.println(arr[i] + "]");15 }16

17 public static int findMax(int[] arr) {18 int max =Integer.MIN_VALUE;19 for(int num : arr) {20 max =Math.max(max, num);21 }22 returnmax;23 }24

25 public static int findMin(int[] arr) {26 int min =Integer.MAX_VALUE;27 for(int num : arr) {28 min =Math.min(min, num);29 }30 returnmin;31 }32 }

1、交换算法

1.1 冒泡算法

整体思路:第i轮排序,从第0号元素到第(length-1-i)号元素,进行相邻元素的比较,并将较大的元素不断交换到后一位置。

特点:第i轮排序完成后,第i个最大值出现在下标为(length-1-i)的位置。

时间复杂度:最坏:O(n^2),平均:O(n^2),最好:O(n);

空间复杂度:O(1)

稳定性:稳定,但是当条件变为arr[j] >= arr[j + 1]时,就不稳定了

Java实现:

1 /**

2 *3 *4 * 以整型数组为例,通过冒泡排序将数组中的元素按从小到大的顺序进行排列5 * 思路:第i轮排序,从第0号元素到第(length-1-i)号元素,进行相邻元素的比较,并将较大的元素交换到后一位置。6 * 特点:第i轮排序结束后,第i个最大值出现在下标为(length-1-i)的位置7 * 时间复杂度:最坏:O(n^2),平均:O(n^2),最好:O(n);8 * 空间复杂度:O(1)9 * 稳定性:稳定,但是当条件变为arr[j] >= arr[j + 1]时,就不稳定了10 */

11 public classBubbleSort {12 public static void bubbleSort(int[] arr) {13 for (int i = 0; i < arr.length - 1; i++) {14 for (int j = 0; j < arr.length - 1 - i; j++) {15 if (arr[j] > arr[j + 1]) {16 Tools.swap(arr, j, j + 1);17 }18 }19 }20 }21

22 public static voidmain(String[] args) {23 int[] arr = { 5, 6, 8, 3, 2, 1, 9, 7, 2};24 System.out.println("before sorting:");25 Tools.printArray(arr);26 bubbleSort(arr);27 System.out.println("after sorting:");28 Tools.printArray(arr);29 }30 }

1.2 快速排序

整体思路:通过递归的方式,不断将数组分为两部分,主元左边的都比主元小,右边的都比主元大。特点:每一轮的排序结束后,数组中的某一部分就会成为有序的序列。

时间复杂度:最坏O(n^2),平均O(nlog2n),最好O(nlog2n)。

空间复杂度:O(nlog2n),主要是由于递归调用造成的栈内存的使用。

稳定性:不稳定。

在每一轮的排序过程中,注意首先从右往左进行查找;在跳出循环后,将low索引指向的位置作为主元交换的位置。

Java实现:

1 /**

2 *3 *4 * 以整型数组为例,通过快速排序将数组中的元素按从小到大的顺序进行排列5 * 思路:通过递归的方式,不断将数组分为两部分,主元左边的都比主元小,右边的都比主元大。6 * 特点:每一轮的排序结束后,数组中的某一部分就会成为有序的序列。7 * 时间复杂度:最坏O(n^2),平均O(nlog2n),最好O(nlog2n)8 * 空间复杂度:O(nlog2n),主要是由于递归调用造成的栈内存的使用9 * 稳定性:不稳定10 *11 * 在每一轮的排序过程中,注意首先从右往左进行查找;在跳出循环后,将low索引指向的位置作为主元交换的位置12 *13 */

14 public classQuickSort {15 public static void quickSort(int[] arr, int first, intlast) {16 if (last low) {23 while (high > low && arr[high] >=pivot)24 high--;25 while (high > low && arr[low] <=pivot)26 low++;27 if (high >low) {28 Tools.swap(arr, low, high);29 }30 }31

32 Tools.swap(arr, first, low);33 quickSort(arr, first, low - 1);34 quickSort(arr, low + 1, last);35 }36

37 public static void quickSort(int[] arr) {38 quickSort(arr, 0, arr.length - 1);39 }40

41 public static voidmain(String[] args) {42 int[] arr = { 1, 5, 6, 8, 3, 2, 1, 9, 7, 2, 1, 1};43 //int[] arr = { 5, 6, 8, 3, 2, 1, 9, 7, 2 };

44 System.out.println("before sorting:");45 Tools.printArray(arr);46 quickSort(arr);47 System.out.println("after sorting:");48 Tools.printArray(arr);49 }50 }

2. 选择排序

2.1 简单选择排序

整体思路:第i轮排序,找到第i个最小值,将其交换到下标为i的位置。

特点:第i轮排序结束后,第i个最小值出现在下标为i的位置。

时间复杂度:三种情况都是O(n^2)。

空间复杂度:O(1)。

稳定性:不稳定

在每一轮选择最小元素的过程中,可以在每次符合条件时都进行换位,也可以将这一轮中最小值的索引记下来,这一轮结束时进行一次换位。这样减少了在堆内存中的操作,更加高效。

Java实现:

1 /**

2 *@authorhr3 *4 * 以整型数组为例,通过选择排序将数组中的元素按从小到大的顺序进行排列5 * 思路:第i轮排序,找到第i个最小值,将其交换到下标为i的位置。6 * 特点:第i轮排序结束后,第i个最小值出现在下标为i的位置7 *8 * 在每一轮选择最小元素的过程中,可以在每次符合条件时都进行换位,也可以将这一轮中最小值的9 * 索引记下来,这一轮结束时进行一次换位。这样减少了在堆内存中的操作,更加高效。10 * 时间复杂度:三种情况都是O(n^2)11 * 空间复杂度:O(1)12 * 稳定性:不稳定13 *14 */

15 public classSelectionSort {16 public static void selectionSort(int[] arr) {17 for (int i = 0; i < arr.length - 1; i++) {18 int minIndex =i;19 for (int j = i + 1; j < arr.length; j++) {20 if (arr[j]

28 public static voidmain(String[] args) {29 int[] arr = { 5, 6, 8, 3, 2, 1, 9, 7, 2};30 System.out.println("before sorting:");31 Tools.printArray(arr);32 selectionSort(arr);33 System.out.println("after sorting:");34 Tools.printArray(arr);35 }36 }

2.2 堆排序

整体思路:首先,将给定的数组建立起一个大顶堆,这也意味着找到了最大的元素;然后,将最大的元素换到堆的末尾(也就是数组的末尾),将剩下的元素重新建立起一个大顶堆,不断找到剩余元素中的最大值。

特点:第i轮排序结束后,第i个最大值出现在下标为length - i的位置 。

时间复杂度:三种情况都是O(nlog2n)。

空间复杂度:O(1)。

稳定性:不稳定。

在最开始建立大顶堆时,只需要遍历前半部分数组即可,初始的循环遍历条件设为(length - 1) / 2,直到0为止。随后每次找最大值,也是从后面的元素开始向上遍历。而对大顶堆进行调整的时候,则是从给定的元素开始不断向下进行调整。每次对堆进行调整的循环停止条件为:当前节点不存在左孩子节点,或者当前节点值已经是三个节点中的最大值了。

Java实现:

/***@authorhr

*

* 以整型数组为例,通过堆排序将数组中的元素按从小到大的顺序进行排列

* 思路:首先,将给定的数组建立起一个大顶堆,这也意味着找到了最大的元素;然后,将最大的元素换到堆的末尾

* (也就是数组的末尾),将剩下的元素重新建立起一个大顶堆,不断找到剩余元素中的最大值。

* 特点:第i轮排序结束后,第i个最大值出现在下标为length - i的位置

*

* 在最开始建立大顶堆时,只需要遍历前半部分数组即可,初始的循环遍历条件设为(length - 1) / 2,直到0为止

* 随后每次找最大值,也是从后面的元素开始向上遍历。

* 而对大顶堆进行调整的时候,则是从给定的元素开始不断向下进行调整。

* 时间复杂度:三种情况都是O(nlog2n)

* 空间复杂度:O(1)

* 稳定性:不稳定

**/

public classHeapSort {private static void heapify(int[] arr, int index, intheapSize) {int left = 2 * index + 1;int right = 2 * index + 2;int largest =index;while (left arr[largest])

largest=left;if (right < heapSize && arr[right] >arr[index])

largest=right;if (index !=largest) {

Tools.swap(arr, index, largest);

index=largest;

left= 2 * index + 1;

right= 2 * index + 2;

}else

break;

}

}public static void buildHeap(int[] arr) {for (int i = (arr.length - 1) / 2; i >= 0; i--) {

heapify(arr, i, arr.length);

}

}public static void heapSort(int[] arr) {

buildHeap(arr);for (int i = arr.length - 1; i >= 0; i--) {

Tools.swap(arr,0, i);

heapify(arr,0, i);

}

}public static voidmain(String[] args) {int[] arr = { 5, 6, 8, 3, 2, 1, 9, 7, 2};

System.out.println("before sorting:");

Tools.printArray(arr);

heapSort(arr);

System.out.println("after sorting:");

Tools.printArray(arr);

}

}

3. 插入排序

3.1 简单插入排序

整体思路:

第i轮排序,是将第i号元素插入由0,1,2...(i-1)号元素组成的有序表中。

特点:第i轮排序结束后,由0,1,2...i号元素组成的序列是有序的。

时间复杂度:最坏(数组是反序的)O(n^2),平均O(n^2),最好(数组本身就是正序的)O(n)。

空间复杂度:O(1)。

稳定性:稳定。

由于第i个元素之前都是有序的,因此在插入第i个元素的时候,可以从前往后遍历,找到合适的位置就停止。

Java实现:

1 /**

2 *@authorhr3 *4 * 以整型数组为例,通过插入排序将数组中的元素按从小到大的顺序进行排列5 * 思路:第i轮排序,是将第i号元素插入由0,1,2...(i-1)号元素组成的有序表中6 * 特点:第i轮排序结束后,由0,1,2...i号元素组成的序列是有序的7 * 时间复杂度:最坏(数组是反序的)O(n^2),平均O(n^2),最好(数组本身就是正序的)O(n)8 * 空间复杂度:O(1)9 * 稳定性:稳定10 *11 * 由于第i个元素之前都是有序的,因此在插入第i个元素的时候,可以从前往后遍历,12 * 找到合适的位置就停止13 */

14 public classInsertionSort {15 public static void insertionSort(int[] arr) {16 for (int i = 1; i < arr.length; i++) {17 int curNum =arr[i];18 intj;19 for (j = i - 1; j >= 0 && arr[j] > curNum; j--) {20 arr[j + 1] =arr[j];21 }22 arr[j + 1] =curNum;23 }24 }25

26 public static voidmain(String[] args) {27 int[] arr = { 5, 6, 8, 3, 2, 1, 9, 7, 2};28 System.out.println("before sorting:");29 Tools.printArray(arr);30 insertionSort(arr);31 System.out.println("after sorting:");32 Tools.printArray(arr);33 }34 }

3.2 希尔排序

整体思路:初始将数组均分为gap组,所有下标之差为gap的元素在同一组内,然后对每一组进行插入排序;最后不断调整gap的值进行循环,直到gap为0。

时间复杂度:最坏O(n^2),平均O(n^1.3),最好O(n)。

空间复杂度:O(1)。

稳定性:不稳定。

实际实现的时候,并不是一组插入排序结束之后再进行另外一组,而是每一组交叉进行。

Java实现:

1 /**

2 *@authorhr3 *4 * 以整型数组为例,通过希尔排序将数组中的元素按从小到大的顺序进行排列5 * 思路:初始将数组均分为gap组,所有下标之差为gap的元素在同一组内,然后对每一组进行插入排序;6 * 最后不断调整gap的值进行循环。7 *8 *9 * 实际实现的时候,并不是一组插入排序结束之后再进行另外一组,而是每一组交叉进行10 * 时间复杂度:最坏O(n^2),平均O(n^1.3),最好O(n)11 * 空间复杂度:O(1)12 * 稳定性:不稳定13 *14 */

15

16 public classShellSort {17 public static void ShellSort(int[] arr) {18 for (int gap = arr.length / 2; gap > 0; gap /= 2) {19 for (int i = gap; i < arr.length; i++) {20 for (int j = i - gap; j >= 0; j -=gap) {21 if (arr[j] > arr[j +gap]) {22 Tools.swap(arr, j, j +gap);23 }24 }25 }26 }27 }28

29 public static voidmain(String[] args) {30 int[] arr = { 5, 6, 8, 3, 2, 1, 9, 7, 2};31 System.out.println("before sorting:");32 Tools.printArray(arr);33 ShellSort(arr);34 System.out.println("after sorting:");35 Tools.printArray(arr);36 }37 }

4. 归并排序

整体思路:归并排序采用的是分治法。首先不断将数组分为左右两部分,直到每部分都只包含一个元素为止。然后对所有的子序列两两合并,最终得到有序序列。

时间复杂度:最坏O(nlog2n),平均O(nlog2n),最好O(nlog2n)。

空间复杂度:O(n)。

稳定性:稳定。

Java实现:

1 /**

2 *@authorhr3 *4 * 以整型数组为例,通过归并排序将数组中的元素按从小到大的顺序进行排列5 * 思路:归并排序采用的是分治法。首先不断将数组分为左右两部分,直到每部分都只包含一个元素为止。6 * 然后对所有的子序列两两合并,最终得到有序序列。7 *8 * 时间复杂度:最坏O(nlog2n),平均O(nlog2n),最好O(nlog2n)9 * 空间复杂度:O(n)10 * 稳定性:稳定11 *12 */

13 public classMergeSort {14 //将arr[first~mid]和arr[mid~last]合并到tmp中

15 public static void mergeArray(int[] arr, int first, int mid, intlast) {16 int[] tmp = new int[last - first + 1];17 int begin1 =first;18 int begin2 = mid + 1;19 int end1 =mid;20 int end2 =last;21 int k = 0;22 while (begin1 <= end1 && begin2 <=end2) {23 if (arr[begin1]

36 for (begin1 = 0; begin1 < k; begin1++) {37 arr[first + begin1] =tmp[begin1];38 }39 }40

41 public static void mergeSort(int[] arr, int first, intlast) {42 if (last - first > 0) {43 int mid = (last + first) / 2;44 mergeSort(arr, first, mid);45 mergeSort(arr, mid + 1, last);46 mergeArray(arr, first, mid, last);47 }48 }49

50 public static void mergeSort(int[] arr) {51 mergeSort(arr, 0, arr.length - 1);52 }53

54 public static voidmain(String[] args) {55 int[] arr = { 5, 6, 8, 3, 2, 1, 9, 7, 2};56 System.out.println("before sorting:");57 Tools.printArray(arr);58 mergeSort(arr);59 System.out.println("after sorting:");60 Tools.printArray(arr);61 }62 }

5.计数排序

整体思路:统计出数组中每个元素出现的次数,然后将元素按顺序填入就可得到有序序列。

时间复杂度:三种情况都是O(n + k)。

空间复杂度:O(n + k),k代表数组的宽度。

稳定性:稳定。

在统计每个元素出现的次数时,首先找到数组的宽度(数组最大值和最小值之差再加一),这样,统计数组中的下标就和元素值联系在一起(相差为min)。得到统计值之后,统计数组中的下标可以找到原数组中对应的元素,统计数组的元素值代表原数组对应元素的出现次数

Java实现:

1 /**

2 *@authorhr3 *4 * 以整型数组为例,通过计数排序将数组中的元素按从小到大的顺序进行排列5 * 思路:统计出数组中每个元素出现的次数,然后将元素按顺序填入就可得到有序序列6 *7 * 在统计每个元素出现的次数时,首先找到数组的宽度(数组最大值和最小值之差再加一),8 * 这样,统计数组中的下标就和元素值联系在一起(相差为min)。得到统计值之后,统计9 * 数组中的下标可以找到原数组中对应的元素,统计数组的元素值代表原数组对应元素的出现次数10 *11 * 时间复杂度:三种情况都是O(n + k)12 * 空间复杂度:O(n + k),k代表数组的宽度13 * 稳定性:稳定14 *15 */

16 public classCountSort {17

18 public static void countSort(int[] arr) {19 int max =Tools.findMax(arr);20 int min =Tools.findMin(arr);21 int[] count = new int[max - min + 1];22 for (intnum : arr) {23 count[num - min]++;24 }25 int index = 0;26 for (int i = 0; i < count.length; i++) {27 while (count[i] != 0) {28 arr[index++] = i +min;29 count[i]--;30 }31 }32 }33

34 public static voidmain(String[] args) {35 int[] arr = { 5, 6, 8, 3, 2, 1, 9, 7, 2};36 System.out.println("before sorting:");37 Tools.printArray(arr);38 countSort(arr);39 System.out.println("after sorting:");40 Tools.printArray(arr);41 }42 }

6. 桶排序

整体思路:根据数组的宽度,生成数量合适的桶。遍历数组,将元素放入对应的桶中。然后对每个桶进行排序,这样,桶内和桶间就都是有序的了。

时间复杂度:最坏:O(n^2),平均:O(n + k),最好:O(n)。

空间复杂度:O(n + k),其中k为数组的宽度。

稳定性:稳定。

实现时,用list代表一个桶,将桶的数量设置为(max - min) / arr.length + 1,这样,(num - min) / arr.length = i的元素就放在第i个桶中。桶内元素我们利用简单插入排序。

Java实现:

1 /**

2 *@authorhr3 *4 * 以整型数组为例,通过桶排序将数组中的元素按从小到大的顺序进行排列5 * 思路:根据数组的宽度,生成数量合适的桶。遍历数组,将元素放入对应的桶中。然后对每个桶进行排序,6 * 这样,桶内和桶间就都是有序的了。7 *8 * 实现时,用list代表一个桶,将桶的数量设置为(max - min) / arr.length + 1,这样,9 * (num - min) / arr.length = i的元素就放在第i个桶中。桶内元素我们利用简单插入排序。10 *11 * 时间复杂度:最坏:O(n^2),平均:O(n + k),最好:O(n);12 * 空间复杂度:O(n + k),其中k为数组的宽度13 * 稳定性:稳定14 */

15 public classBucketSort {16 public static void insertionSort(ArrayListbucket) {17 for (int i = 1; i < bucket.size(); i++) {18 intj;19 int curNum =bucket.get(i);20 for (j = i - 1; j >= 0 && bucket.get(j) > curNum; j--) {21

22 bucket.set(j + 1, bucket.get(j));23

24 }25 bucket.set(j + 1, curNum);26 }27 }28

29 public static void bucketSort(int[] arr) {30 int max =Integer.MIN_VALUE;31 int min =Integer.MAX_VALUE;32 max =Tools.findMax(arr);33 min =Tools.findMin(arr);34 int bucketNum = (max - min) / arr.length + 1;35 ArrayList> buckets = new ArrayList<>();36 for (int i = 0; i < bucketNum; i++) {37 buckets.add(new ArrayList<>());38 }39

40 for (intnum : arr) {41 int index = (num - min) /arr.length;42 buckets.get(index).add(num);43 }44

45 int index = 0;46 for (int i = 0; i < buckets.size(); i++) {47 insertionSort(buckets.get(i));48 for (intnum : buckets.get(i)) {49 arr[index++] =num;50 }51 }52 }53

54 public static voidmain(String[] args) {55 int[] arr = { 5, 6, 8, 3, 2, 1, 9, 7, 2};56 System.out.println("before sorting:");57 Tools.printArray(arr);58 bucketSort(arr);59 System.out.println("after sorting:");60 Tools.printArray(arr);61 }62 }

7. 基数排序

整体思路:本质上和桶排序是一样的,只是这个桶是固定的10个。排序的过程,就是将所有元素,从个位开始,不断进行桶排序。当所有的数位都排序一遍之后,就得到了有序序列。

时间复杂度:三种情况都是O(n + k)。

空间复杂度:O(n + k),其中k为数组的宽度。

稳定性:稳定。

实现时,可以用list代表一个基数的计数桶;也可以用一个和原数组等长的临时数组,和一个大小为10的计数数组来进行统计。但是用后者会在一次排序的过程中,遍历两遍数组。

Java实现:

1 /**

2 *@authorhr3 *4 * 以整型数组为例,通过基数排序将数组中的元素按从小到大的顺序进行排列5 * 思路:本质上和桶排序是一样的,只是这个桶是固定的10个。排序的过程,就是将所有元素,从个位开始,6 * 不断进行桶排序。当所有的数位都排序一遍之后,就得到了有序序列。7 *8 * 实现时,可以用list代表一个基数的计数桶;也可以用一个和原数组等长的临时数组,9 * 和一个大小为10的计数数组来进行统计。但是用后者会在一次排序的过程中,遍历两遍数组10 *11 * 时间复杂度:三种情况都是O(n + k);12 * 空间复杂度:O(n + k),其中k为数组的宽度13 * 稳定性:稳定14 */

15 public classRadixSort {16 public static void radixSort(int[] arr) {17 ArrayList> tmp = new ArrayList<>();18 for (int i = 0; i < 10; i++) {19 tmp.add(new ArrayList<>());20 }21

22 int maxNum =Tools.findMax(arr);23 int maxLength =Integer.toString(maxNum).length();24

25 int radix = 1;26 for (int i = 0; i < maxLength; i++) {27 for (int k = 0; k < 10; k++) {28 tmp.add(new ArrayList<>());29 }30

31 for (intnum : arr) {32 int digit = (num / radix) % 10;33 tmp.get(digit).add(num);34 }35

36 int index = 0;37 for (int j = 0; j < tmp.size(); j++) {38 for (intnum : tmp.get(j)) {39 arr[index++] =num;40 }41 }42

43 radix *= 10;44 tmp.clear();45 }46 }47

48 public static voidmain(String[] args) {49 int[] arr = { 15, 6, 8, 3, 2, 1, 9, 7, 2};50 System.out.println("before sorting:");51 Tools.printArray(arr);52 radixSort(arr);53 System.out.println("after sorting:");54 Tools.printArray(arr);55 }56 }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值