排序算法是编程中最常用的算法
下面将一一实现并分析各种排序算法
冒泡排序:
算法
private static void sort(Comparable[] a){
int length=a.length;
for(int i = 0;i<length;i++){
for(int j = 1;j<length-i;j++){
if(less(a[j],a[j-1])){
exch(a,j-1,j);
}
}
}
}
注:exch(a,j-1,j):交换a[j-1],a[j]的位置
less(a[j],a[j-1]) :比较a[j],a[j-1]的大小,前者小于后者则返回true,反之返回false【以下代码都一样,就不做重复解释】
每一次遍历都将最大的元素找出并放到最后,所以冒泡从而得名。复杂度可以说是是有点高了,但是冒泡排序很稳定.
冒泡排序:时间复杂度为O(n^2),
选择排序:
这种排序比较简单,每次找出最小或者最大的即可,就不多解释,直接贴出代码:
private static void selectSort(Comparable[] a){
int length = a.length;
for(int i = 0;i<length;i++){
int min = i;
for(int j = i+1;j<length;j++){
if(less(a[j],a[min]))
min = j;
exch(a,i,min);
}
}
}
插入排序:
- 直接插入排序:
先看算法
public static void insertSort(Comparable[] a){
int length = a.length;
for(int i = 1; i<length; i++){
for(int j = i;j>0 && less(a[j],a[j-1]);j--){//保证[0-i]有序。可以采用折半插入减少一定的比较次数(下面会讲,这里先用原始的一个一个元素的比较)
exch(a, j, j-1);
}
}
}
- 折半插入排序:
当要插入的序列是有序时,我们可以采用折半插入排序来插入,可以很大程度上减少运算次数,下面我对上面的直接插入排序算法进行改进
public static void binaryInsertSort(int[] data) {
for (int i = 1; i < data.length; i++) {
if (data[i] < data[i - 1]) {
// 缓存i处的元素值
int tmp = data[i];
// 记录搜索范围的左边界
int low = 0;
// 记录搜索范围的右边界
int high = i - 1;
while (low <= high) {
// 记录中间位置
int mid = (low + high) / 2;
// 比较中间位置数据和i处数据大小,以缩小搜索范围
if (data[mid] < tmp) {
low = mid + 1;
} else {
high = mid - 1;
}
}
//将low~i处数据整体向后移动1位
for (int j = i; j > low; j--) {
data[j] = data[j - 1];
}
data[low] = tmp;
print(data);
}
}
}
堆排序
堆排序,我们需要引入一个新的概念:优先队列
许多应用程序都需要处理有序,但不一定要求他们都全部有序,或是不一定要一次就将他们排序。很多情况下,我们会收集一些,处理当前键值最大的元素,然后再收集更多的元素,再处理当前键值最大的元素,如此这般。例如,你有可能有一台同时运行多个应用程序的电脑。这是通过为每个应用程序的事件分配一个优先级,,并总是处理下一个优先级最高的事件来实现。此时,并可以引用优先队列这种数据结构来实现:只支持两种操作:删除最大元素和插入元素。
优先队列可以采用数组或者链表来实现(利用栈的特性)
算法如下:
public class Solution {
Stack<Integer> dataStack = new Stack<>();//数据栈
Stack<Integer> minStack = new Stack<>();//辅助栈,每次将最小和次小的元素存在此栈中
public void push(int node) {
dataStack.push(node);
//if(minStack.isEmpty()||minStack.peek()>node){
// minStack.push(node);
//}else {
// minStack.push(minStack.top());
//}
minStack.push(minStack.isEmpty() ? node : Math.min(minStack.peek(),node));
}
public void pop() {
dataStack.pop();
minStack.pop();
}
public int top() {
//peek()返回栈顶的值但不删除pop()会删除
return dataStack.peek();
}
public int min() {
return minStack.peek();
}
}
通过上面的描述,我们发现操作起来还是不很流畅,插入和删除需要线性时间才能完成,这还是不够快,因此,我们引入新的数据结构–堆。我们构建基于二叉堆的优先队列
先来看看堆的两种算法:
- 下沉
//从上至下的下沉算法
private void sink(int k){
while(2*k <= N){//
int j = 2*k;
if(j<N && less(j,j+1)) j++;
if(!less(k,j)) break;
k=j;
}
}
上浮算法
private void swim(int k){
while(k>1 && less(k/2,k)){
swap(k/2,k);
k = k/2;
}
}
基于堆的优先队列:
public class Heap<T extends Comparable<T>> {
private T[] heap;
private int N=0;
public Heap(int maxN) {
this.heap = (T[]) new Comparable[maxN+1];
}
public boolean isEmpty() {
return N==0;
}
public int size() {
return N;
}
private boolean less(int i,int j){
return heap[i].compareTo(heap[j])<0;
}
private void swap(int i, int j){
T t = heap[i];
heap[i] = heap[j];
heap[j] = t;
}
private void swim(int k){
while(k>1 && less(k/2,k)){
swap(k/2,k);
k = k/2;
}
}
private void sink(int k){
while(2*k <= N){
int j = 2*k;
if(j<N && less(j,j+1)) j++;
if(!less(k,j)) break;
k=j;
}
}
/**
* 向二叉堆里插入新元素
* @param v
*/
public void insert(Comparable v){
heap[++N] = (T) v;
swim(N);
}
/**
* 删除最大元素
* @return
*/
public T delMax() {
T max = heap[1];
swap(1,N--);
heap[N+1] = null;
sink(1);
return max;
}
}
有了堆的优先队列,我们就来看看堆排序的实
public static void heapSort(Comparable[] a){
int length = a.length-1;
for(int k = length/2;k>=1;k--){//下沉排序
sink(a, k ,length);
}
while (length > 1) {
exch(a,1,length--);
sink(a,1,length);
}
}
快速排序:快速排序是一种分治的思想
先来看看快速排序切分的思想:
private static int partition(Comparable[] a,int start,int end){
int i = start,j=end+1;
Comparable p = a[start];
while (true) {
while(less(a[++i],p) && i != end);//从后往前扫
while(less(p,a[--j]) && j != start);//从前往后扫
if (i >=j)
break;
exch(a,i,j);
}
exch(a,start,j);
return j;
}
快速排序
private static void quickSort(Comparable[] a,int start,int end){
if(end <= start)
return ;
int j = partition(a,start,end);
quickSort(a,start,j-1);
quickSort(a,j+1,end);
}
希尔排序
希尔排序又叫“`缩小增量排序。其本质其实还是插入排序,但是这里选取了一个增量,来加速排序进程。
public static void shellSort(Comparable[] a ){
int length = a.length;//eg.a={1,2,3,52,21,11,5,9,15}
int h = 1;//增量
while(h<length/3)
h = h*3+1;//h=4
while(h>=1)
{
//将数组有序
for(int i=h;i<length;i++){
for(int j=i;j>=h && less(a[j],a[j-h]);j-=h){
exch(a,j,j-h)//对增量h有序化
}
}
h = h/3;//h=4/3=1
}
}
归并排序:
归并排序是指将每种排序分别排序,然后不断选取合适的范围进行整合排序,这里采用了动态规划的分治思想:分而治之。
private static void merge(Comparable[] a,int start,int m,int end){
int i = start,j = m + 1;
for (int k = start; k <= end; k++){
aux[k] = a[k];
}
for (int k = start; k <= end; k++){
if(i> m) a[k] = aux[j++];
else if(j>end) a[k] = aux[i++];
else if(less(aux[j],aux[i])) a[k] = aux[j++];
else a[k] = aux[i++];
}
}
private static void mergeSort(Comparable[] a, int start,int end){
if (end <= start) return;
int m = start + (end-start)/2;
mergeSort(a,start,m);
mergeSort(a,m+1,end);
merge(a,start,m,end);
}
桶排序:
这是一个比叫有趣的排序,也称基数排序。基数排序的思想是:“多关键字排序”
public static void bucketSort(int[] arr){
//分桶,这里采用映射函数f(x)=x/10。
//输入数据为0~99之间的数字
int bucketCount =10;
Integer[][] bucket = new Integer[bucketCount][arr.length]; //Integer初始为null,以与数字0区别。
for (int i=0; i<arr.length; i++){
int quotient = arr[i]/10; //这里即是使用f(x)
for (int j=0; j<arr.length; j++){
if (bucket[quotient][j]==null){
bucket[quotient][j]=arr[i];
break;
}
}
}
//小桶排序
for (int i=0; i<bucket.length; i++){
//insertion sort
for (int j=1; j<bucket[i].length; ++j){
if(bucket[i][j]==null){
break;
}
int value = bucket[i][j];
int position=j;
while (position>0 && bucket[i][position-1]>value){
bucket[i][position] = bucket[i][position-1];
position--;
}
bucket[i][position] = value;
}
}
//输出
for (int i=0, index=0; i<bucket.length; i++){
for (int j=0; j<bucket[i].length; j++){
if (bucket[i][j]!=null){
arr[index] = bucket[i][j];
index++;
}
else{
break;
}
}
}
}
排序算法总结:
算法 | 稳定 | 时间复杂度 | 空间复杂度 | 备注 |
---|---|---|---|---|
选择排序 | no | N2 | 1 | |
冒泡排序 | yes | N2 | 1 | |
插入排序 | yes | N \~ N2 | 1 | 时间复杂度和初始顺序有关 |
希尔排序 | no | N 的若干倍乘于递增序列的长度 | 1 | |
快速排序 | no | NlogN | logN | |
三向切分快速排序 | no | N \~ NlogN | logN | 适用于有大量重复主键 |
归并排序 | yes | NlogN | N | |
堆排序 | no | NlogN | 1 |