十大经典排序算法(java实现)
前言
本文我们将以java代码实现十大经典排序算法,包括冒泡排序、选择排序、插入排序、希尔排序、归并排序、快速排序、堆排序、计数排序、桶排序、基数排序。
排序算法相关的时间复杂度和空间复杂度
1、冒泡排序(Bubble Sort)
算法描述
- 比较相邻的元素。如果第一个比第二个大,就交换它们两个;
- 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
- 针对所有的元素重复以上的步骤,除了最后一个;
- 重复步骤1~3,直到排序完成。
代码实现
public void sort(int[] nums) {
for(int i = 0;i<nums.length -1;i++){
for (int j = 0; j < nums.length - 1-i; j++) {
int c;
if (nums[j] > nums[j + 1]) {
c = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = c;
}
}
}
}
这里有个问题,冒泡排序的时间复杂度最好是n,但上述代码不管如何时间复杂度都是n²,所以有了以下改进算法。
public void bubbleSort(int arr[]) {
boolean didSwap;
for(int i = 0, len = arr.length; i < len - 1; i++) {
didSwap = false;
for(int j = 0; j < len - i - 1; j++) {
if(arr[j + 1] < arr[j]) {
int temp = arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
didSwap = true;
}
}
if(didSwap == false)
return;
}
}
2、选择排序(Selection Sort)
算法描述
选择排序一种简单直观的排序算法。首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
代码实现
public int[] sort(int[] nums) {
int len = nums.length;
int temp;
for(int i = 0;i<len;i++){
for(int j =i;j<len;j++){
if(nums[i]>nums[j]){
temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
}
return nums;
}
3、插入排序(Insertion Sort)
算法描述
- 从第一个元素开始,该元素可以认为已经被排序;
- 取下一元素,在已经排序的元素序列中从后向前扫描;
- 如果该元素(已排序)大于新元素,将该元素移到下一位置;
- 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
- 将新元素插入到该位置后;
- 重复步骤2~5。
代码实现
public void sort(int[] nums) {
int len = nums.length;
int preIndex,current;
for(int i = 0;i<len;i++){
preIndex = i - 1;
current = nums[i];
while(preIndex >= 0 && nums[preIndex] > current){
nums[preIndex + 1] = nums[preIndex];
preIndex--;
}
nums[preIndex + 1] = current;
}
}
4、希尔排序(Shell Sort)
算法排序
希尔排序(Shell's Sort)是插入排序)的一种又称“缩小增量排序”,是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。
- 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
- 按增量序列个数k,对序列进行k 趟排序;
- 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
代码实现
public void sort(int[] nums) {
int len = nums.length;
for (int gap = (int) Math.floor(len / 2); gap > 0; gap = (int) Math.floor(gap / 2)) {
// 注意:这里和动图演示的不一样,动图是分组执行,实际操作是多个分组交替执行
for (int i = gap; i < len; i++) {
int j = i;
int current = nums[i];
while (j - gap >= 0 && current < nums[j - gap]) {
nums[j] = nums[j - gap];
j = j - gap;
}
nums[j] = current;
}
}
}
5、归并排序(Merge Sort)
算法描述
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。归并排序是一种稳定的排序方法。实际上就是用的递归的思想。
- 把长度为n的输入序列分成两个长度为n/2的子序列;
- 对这两个子序列分别采用归并排序;
- 将两个排序好的子序列合并成一个最终的排序序列。
代码实现
public int[] sort(int[] nums) {
int len = nums.length;
if(len<2){
return nums;
}
int middle = (int) Math.floor(len/2);
int[] left = new int[middle],right = new int[len-middle];
System.arraycopy(nums, 0, left, 0, middle);
System.arraycopy(nums, middle, right, 0, len-middle);
return merge(sort(left),sort(right));
}
private int[] merge(int[] left, int[] right) {
// TODO Auto-generated method stub
int[] result = new int[left.length+right.length];
int i = 0, j = 0, k = 0;
while ((left.length - j) > 0 && (right.length - k) > 0) {
if (left[j] <= right[k]) {
result[i] = left[j];
j++;
} else {
result[i] = right[k];
k++;
}
i++;
}
while (j < left.length){
System.arraycopy(left, j, result, i, left.length-j);
j++;
i++;
}
while (k < right.length){
System.arraycopy(right, k, result, i, right.length-k);
k++;
i++;
}
return result;
}
6、快速排序(Quick Sort)
算法描述
快速排序(Quicksort)是对冒泡排序的一种改进。
- 首先选择一个分界值,通过该分界值将数组分成左右两部分。
- 重新对数组进行排序,比分界值小的集中到数组左边,比分界值大的位于数组右边。
- 然后左右两边数组再次进行快速排序。
- 最后重复上述步骤,实现递归。
代码实现
public void sort(int[] arr,int low, int high) {
int i, j ,temp,snap;
if(low > high){
return;
}
i = low;
j = high;
// 基准位置
temp = arr[low];
while (i<j) {
while (temp<=arr[j] && i<j) {
j--;
}
while (temp>=arr[i] && i<j) {
i++;
}
if (i<j) {
// 交换满足条件的 i j位置的值
snap = arr[i];
arr[i] = arr[j];
arr[j] = snap;
}
}
// 交换基准位置的值
arr[low] = arr[i];
arr[i] = temp;
// 递归调用, 左右
sort(arr,low,j-1);
sort(arr,j+1,high);
}
7、堆排序(Heap Sort)
算法描述
堆排序是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
- 将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆
- 将堆顶元素R[1]与最后一个元素R[n]交换。
- 由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。
代码实现
public static void sort(int[] array) {
if (array == null || array.length <= 1) {
return;
}
buildMaxHeap(array);
int temp;
for(int arg:array){
System.out.print(arg+" ");
}
for (int i = array.length - 1; i >= 1; i--) {
temp = array[0];
array[0] = array[i];
array[i] = temp;
maxHeap(array, i, 0);
System.out.println(" ");
System.out.println("-------"+i+"-------");
for(int arg:array){
System.out.print(arg+" ");
}
}
}
private static void buildMaxHeap(int[] array) {
if (array == null || array.length <= 1) {
return;
}
int half = array.length / 2;
for (int i = half; i >= 0; i--) {
maxHeap(array, array.length, i);
}
}
private static void maxHeap(int[] array, int heapSize, int index) {
int left = index * 2 + 1;
int right = index * 2 + 2;
int largest = index;
if (left < heapSize && array[left] > array[index]) {
largest = left;
}
if (right < heapSize && array[right] > array[largest]) {
largest = right;
}
if (index != largest) {
int temp;
temp = array[index];
array[index] = array[largest];
array[largest] = temp;
maxHeap(array, heapSize, largest);
}
}
8、计数排序(Counting Sort)
算法描述
计数排序是一个非基于比较的排序算法,牺牲空间换取时间。
- 得到数组的最大和最小元素。
- 统计出每个数据为i的值出现的次数,将次数存入数组C中。
- 以数组C为模板重新填充目标数组。
代码实现
public void sort(int[] nums) {
int min = nums[0],max = nums[0];
for(int num: nums){
max = Math.max(max, num);
min = Math.min(min, num);
}
int[] counts = new int[max - min+1];
for(int num: nums){
counts[num - min]++;
}
int i = 0,j = 0;
for(int count:counts){
while (count > 0) {
nums[i] = j + min;
count --;
i ++;
}
j++;
}
}
9、桶排序(Bucket Sort)
算法描述
将数组分到有限数量的桶子里。每个桶子再个别排序(有可能再使用别的排序算法)。
- 设置一个定量的链表当作空桶。
- 遍历输入数据,并且把数据一个一个放到对应的桶里去。
- 对每个不是空的桶进行排序。
- 从不是空的桶里把排好序的数据拼接起来。
代码实现
public void sort(int[] nums) {
int min = nums[0],max = nums[0];
for(int num: nums){
max = Math.max(max, num);
min = Math.min(min, num);
}
int bucketBottom = (int) Math.floor((float)min/10);
int bucketTop = (int) Math.ceil((float)max/10);
ArrayList<ArrayList<Integer>> bucketsList = new ArrayList<ArrayList<Integer>>();
for(int i = 0;i<(bucketTop-bucketBottom);i++){
bucketsList.add(new ArrayList<Integer>());
}
for(int num: nums){
int index = getBucketIndex(num);
insertBucket(bucketsList.get(index),num);
}
int index = 0;
for (ArrayList<Integer> list : bucketsList) {
for(int data: list){
nums[index++] = data;
}
}
}
// 将数据放入具体的桶内
private void insertBucket(ArrayList<Integer> arrayList, int num) {
boolean insertFlag = true;
ListIterator<Integer> it = arrayList.listIterator();
while (it.hasNext()) {
if (num <= it.next()) {
it.previous(); // 把迭代器的位置偏移回上一个位置
it.add(num); // 把数据插入到迭代器的当前位置
insertFlag = false;
break;
}
}
// 否则把数据插入到链表末端
if (insertFlag) {
arrayList.add(num);
}
}
// 判断放入哪个桶内的方法
private int getBucketIndex(int data){
return (int) Math.floor(data/10);
}
10、基数排序(Radix Sort)
算法描述
基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。
- 取得数组中的最大数,并取得位数。
- 使用链表对按照当前位数大小存储数据,然后重新写入数组。
- 根据位数的不同重复步骤2。
代码实现
public void sort(int[] nums) {
int max = 0;
int exp = 1;
for (int num:nums){
max = Math.max(max,num);
}
while(max/Math.pow(10,exp) > 1){
exp++;
}
//存储待排元素
ArrayList<ArrayList<Integer>> radixList = new ArrayList<ArrayList<Integer>>();
for(int j = 0;j<10;j++){
ArrayList<Integer> list = new ArrayList<Integer>();
radixList.add(list);
}
for(int i = 0;i<=exp;i++){
for(int num:nums){
int positionValue = getPositionValue(num, i);
radixList.get(positionValue).add(num);
}
int index = 0;
for(ArrayList<Integer> list:radixList){
for(int value: list){
nums[index] = value;
index++;
}
list.clear();
}
}
}
// 获取当前位数对应的值
private int getPositionValue(int num,int exp){
return (int) (num % Math.pow(10, exp)/Math.pow(10, exp-1));
}