八种排序算法--Java实现
- 一、插入排序
- 1.直接插入排序
- 2.二分插入排序
- 3.希尔排序
- 二、选择排序
- 1.简单选择排序
- 2.堆排序
- 三、交换排序
- 1.冒泡排序
- 2.快速排序
- 四、归并排序
- 五、基数排序
一、插入排序
思想:每步将一个待排序的记录,按其顺序码大小插入到前面已经排序的子序列的合适位置,直到全部插入排序完为止。
方法:
1.直接插入法;
2.二分插入排序;
3.希尔排序;
1.直接插入排序
基本思想:
每步将一个待排序的记录,按其顺序码大小插入到前面已经排序的子序列的合适位置(从后向前找到合适位置后),直到全部插入排序完成。
算法实现:
int a[] = {5,2,4,1,8,3,7,9,10,6};
public void insertSort(int []a) {
for(int i = 1; i < a.length;i++) {
//待插入元素
int temp = a[i];
int j;
for(j = i - 1;j >= 0 && a[j] > temp; j--) {
//将大于temp的元素往后移动一位
a[j + 1] = a[j];
}
a[j + 1] = temp;
}
}
复杂度
如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放到相等元素的后面。所以插入排序是稳定的。
时间复杂度O(n^2)
空间复杂度O(1)
2.二分插入排序
基本思想:
二分插入排序的思想和直接插入一样,只是找合适的插入位置的方式不同,这里是按二分法找到合适的位置,可以减少比较的次数。
算法实现:
public static void twoInsertSort(int []a) {
for(int i = 0; i < a.length ;i++) {
int temp = a[i];
int left = 0;
int right = i -1;
int mid;
while(left <= right ) {
mid = (left + right)/2;
if(temp < a[mid]) {
right = mid - 1;
}else {
left = mid +1;
}
}
for(int j = i - 1; j >= left; j--) {
a[j + 1] = a[j];
}
if(left != i) {
a[left] = temp;
}
}
}
3.希尔排序
基本思想:
先取一个小于n的整数d1作为第一个增量,把文件的全部记录分成d1个组。所有距离为d1的倍数的记录分成d1个组。现在各组内进行直接插入排序;然后,取第二个增量d2<d1重复上述的分组和排序,直到所取得增量dt=1(dt<dt-1<dt-2…<d2<d1),即所有记录放在同一组中进行直接插入排序位置。该方法实质上是一种分组插入方法。
算法实现:
public static void ShellSort(int []a) {
int dk = a.length/2;
while(dk >= 1) {
SHellInsertSort(a,dk);
dk = dk/2;
}
}
private static void SHellInsertSort(int[] a, int dk) {
for(int i = dk; i < a.length; i++) {
//带插入元素
int temp = a[i];
int j ;
for(j = i - dk; j >= 0 && a[j] > temp; j = j-dk) {
//将大于temp的往后移动dk位
a[j + dk] = a[j];
}
a[j + dk] = temp;
}
复杂度
希尔排序失效分析很难,关键码的比较次数与记录移动次数,依赖于增量因子序列d的选取,特定情况下可以准确估算出关键码的比较次数和记录的移动次数。目前还没有人给出选取最好的增量因子序列的方法。增量因子序列可以有各种取法,有取奇数,质数,但需要注意的是:增量因子必须为1.希尔排序方法是一个不稳定的排序方法。
时间复杂度O(nlog2n)
空间复杂度O(1)
二、选择排序
思想:每趟从待排序的记录序列中选择关键字最小的记录放置到已排序表的最前面,直到全部排完。
关键问题:在剩余的待排序记录序列中找到最小关键码记录。
方法:
简单选择排序;
二元选择排序;
堆排序;
1.简单选择排序
基本思想:
再要排序的一组数中,选出最小的一个数与第一个数交换位置;然后在剩下的数当中再找最小的与第二个位置的交换位置,如此循环到倒数第二个数和最后一个数做比较。
算法实现:
public static void insertSort(int []a) {
for(int i = 0; i < a.length; i++) {
int min = i;
for(int j = i +1; j < a.length; j++) {//选出之后待排序中值最小的位置
//找到最小值下标
if(a[j] < a[min]) {
min = j;
}
}if(min != i) {
int temp = a[min];
a[min] = a[i];
a[i] = temp;
}
}
}
时间复杂度
O(n^2)
空间复杂度
O(1)
2.堆排序
基本思想:
堆的定义:n个元素的序列{k1,k2,…,kn},当且仅当满足下关系时,称之为堆。
ki<=k(2i) 或 ki >= k(2i)
ki<=k(2i+1) ki>=k(2i+1)
把此序列对应的二维数组看成一个完全二叉树。那么堆的含义就是:**完全二叉树中任何一个非叶子节点的值均不大于(或小于)其左右孩子节点的值。**由上述可知大顶堆的堆顶的 关键字肯定是所有关键字中最大的,小顶堆的堆顶的关键字是所有关键字中最小的。对大顶堆进行升序排序,对小顶堆进行降序排序。
将待排序的序列构成一个堆,选出堆中最大的移走,再把剩余的元素调整成堆,找出最大的再移走,重复直至有序。
代码实现
public static void insertSort(int []a) {
for(int i = a.length; i > 0;i--) {
max_heapify(a,i);
int temp = a[0]; //堆顶元素(第一个元素)与kn交换
a[0] = a[i - 1];
a[i - 1] = temp;
}
}
private static void max_heapify(int[] a, int limit) {
if(a.length <= 0 || a.length < limit) return;
int parentIdx = limit / 2;
for(;parentIdx >= 0;parentIdx--) {
if(parentIdx * 2 >= limit) {
continue;
}
int left = parentIdx * 2; //左节点位置
int right = (left + 1) >= limit ? left : (left + 1);
int maxChildId = a[left] >= a[right] ? left :right;
if(a[maxChildId] > a[parentIdx]) {//交换父节点与左右子节点中的最大值
int temp = a[parentIdx];
a[parentIdx] = a[maxChildId];
a[maxChildId] = temp;
}
}
}
复杂度
时间复杂度为O(logn)
空间复杂度为O(1)
由于堆排序中初始化堆的过程比较次数较多,因此他不太适用于小序列。
三、交换排序
1.冒泡排序
基本思想:
他重复的走访过要排序的数列,一次比较两个元素,如果它们顺序错误就把它们交换过来。走访数列的工作室重复进行知道没有再需要交换,也就是说该数列已经完成排序。
代码实现
public static void insertSort(int []a) {
for(int i = a.length - 1; i > 0; i--) {//外层循环移动游标
for(int j = 0; j < i; j++) {//内循环遍历游标及之后的元素
if(a[j] > a[j + 1]) {
int temp = a[j];
a[j] = a[j + 1];
a[j + 1] = temp;
}
}
}
}
复杂度:
时间复杂度最好O(n)最坏情况下是O(n^2)
空间复杂度O(1)
2.快速排序
基本思想:
挖坑填数+分治法。
首先选一个轴值,通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
代码实现
递归的思想
public static void quickSort(int []a,int low,int high) {
if(a.length <= 0) return;
if(low >= high) return;
int left = low;
int right = high;
int temp = a[left]; //坑1: 保存基准的值
while(left < right) {
while(left < right && a[left] >= temp) { //坑2:从后向前找到比基准小的元素,插入到基准位置坑1中
right --;
}
a[left] = a[right];
while(left < right && a[left] <= temp) {//坑3:从前往后找到比基准大的元素,放到刚才挖的坑2中
left++;
}
a[right] = a[left];
}
a[left] = temp; //基准值填补到坑3中,准备分治递归快排
quickSort(a, low, left - 1);
quickSort(a, left + 1, high);
}
非递归
public static void quickSort(int[] arr){
if(arr.length <= 0) return;
Stack<Integer> stack = new Stack<Integer>();
//初始状态的左右指针入栈
stack.push(0);
stack.push(arr.length - 1);
while(!stack.isEmpty()){
int high = stack.pop(); //出栈进行划分
int low = stack.pop();
int pivotIdx = partition(arr, low, high);
//保存中间变量
if(pivotIdx > low) {
stack.push(low);
stack.push(pivotIdx - 1);
}
if(pivotIdx < high && pivotIdx >= 0){
stack.push(pivotIdx + 1);
stack.push(high);
}
}
}
private static int partition(int[] arr, int low, int high){
if(arr.length <= 0) return -1;
if(low >= high) return -1;
int l = low;
int r = high;
int pivot = arr[l]; //挖坑1:保存基准的值
while(l < r){
while(l < r && arr[r] >= pivot){ //坑2:从后向前找到比基准小的元素,插入到基准位置坑1中
r--;
}
arr[l] = arr[r];
while(l < r && arr[l] <= pivot){ //坑3:从前往后找到比基准大的元素,放到刚才挖的坑2中
l++;
}
arr[r] = arr[l];
}
arr[l] = pivot; //基准值填补到坑3中,准备分治递归快排
return l;
}
四、归并排序
基本思想:
归并排序算法是将两个或两个以上有序表合并成一个新的有序表,即把待排序序列分成若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。
代码实现:
public static int[] mergingSort(int[] arr){
if(arr.length <= 1) return arr;
int num = arr.length / 2;
int[] leftArr = Arrays.copyOfRange(arr, 0, num);
int[] rightArr = Arrays.copyOfRange(arr, num, arr.length);
return mergetwoArray(mergingSort(leftArr),mergingSort(rightArr)); //不断拆分为最小单元,再排序合并
}
private static int[] mergetwoArray(int[] arr1, int[] arr2) {
int i = 0, j = 0, k = 0;
int[] result = new int[arr1.length +arr2.length]; //申请额外的空间存储合并之后的数组。
while(i < arr1.length && j < arr2.length) {
if(arr1[i] <= arr2[j]) {
result[k++] = arr1[i++];
}else {
result[k++] = arr2[j++];
}
}
while(i < arr1.length) { //序列1中多余的元素移入新数组
result[k++] = arr1[i++];
}
while(j < arr2.length) { //序列2中多余的元素移入新数组
result[k++] = arr2[j++];
}
return result;
}
五、基数排序
基本思想:
将所有待比较数(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从低位开始,依次进行一次排序。这样从最低位开始,依次进行依次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成了一个有序序列。
代码实现:
public static void radixSort(int[] arr){
if(arr.length <= 1) return;
//取得数组中的最大数,并取得位数
int max = 0;
for(int i = 0; i < arr.length; i++){
if(max < arr[i]){
max = arr[i];
}
}
int maxDigit = 1;
while(max / 10 > 0){
maxDigit++;
max = max / 10;
}
//申请一个桶空间
int[][] buckets = new int[10][arr.length-1];
int base = 10;
//从低位到高位,对每一位遍历,将所有元素分配到桶中
for(int i = 0; i < maxDigit; i++){
int[] bktLen = new int[10]; //存储各个桶中存储元素的数量
//分配:将所有元素分配到桶中
for(int j = 0; j < arr.length; j++){
int whichBucket = (arr[j] % base) / (base / 10);
buckets[whichBucket][bktLen[whichBucket]] = arr[j];
bktLen[whichBucket]++;
}
//收集:将不同桶里数据挨个捞出来,为下一轮高位排序做准备,由于靠近桶底的元素排名靠前,因此从桶底先捞
int k = 0;
for(int b = 0; b < buckets.length; b++){
for(int p = 0; p < bktLen[b]; p++){
arr[k++] = buckets[b][p];
}
}
base *= 10;
}
}