Files
kaka111222333-kaka111222333…/_posts/2014-09-19-sort.md
2019-11-17 01:12:14 +08:00

434 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
layout: post
title: 八种排序
tags: 算法 algorithm 排序
categories: algorithm
---
* TOC
{:toc}
![八大排序][八大排序]
<br/>
## 1.直接插入排序
基本思想:在要排序的一组数中,假设前面(n-1) [n>=2] 个数已经是排好顺序的现在要把第n个数插到前面的有序数中使得这n个数也是排好顺序的。如此反复循环直到全部排好顺序。
![插入排序][插入排序]
~~~java
/**
* 形象描述:踢馆
* 直接插入排序(从小到大)
* @param src 待排序的数组
*/
public void straight_insertion_sort(int[] src){
int tmp;
for (int i = 0; i < src.length; i++) {
tmp = src[i];
int j = i - 1;
//向前匹配,凡是比自己大的都往后移一位,碰到不比自己大的直接跳出
for (; j >= 0 && tmp<src[j] ; j--) {
src[j+1] = src[j];
}
//j-1位置匹配失败,在后一位插入
src[j+1] = tmp;
}
}
~~~
<br/>
## 2.希尔排序(缩小增量排序)
基本思想算法先将要排序的一组数按某个增量dn/2,n为要排序数的个数分成若干组每组中记录的下标相差d.对每组中相同位置的元素进行直接插入排序然后再用一个较小的增量d/2对它进行分组再对每组中相同位置的元素进行直接插入排序。当增量减到1时进行直接插入排序后排序完成。
![希尔排序][希尔排序]
~~~java
/**
* 希尔排序(缩小增量排序)(从小到大)
* @param src
*/
public void shell_sort(int[] src){
int inc = src.length;
int tmp;
while(true){
inc = (int)Math.ceil(inc/2);//增量,亦即每组长度
//遍历每组进行排序的元素索引,具体看内部注释
for (int x = 0; x < inc; x++) {
//以下内容即插入排序,但是搜索头步进inc,回顾时步退inc
//这是一个模拟,可以想象为:将src分组,每组长度为inc,然后对每组的第0,1,2,3...个元素分别进行插入排序
//即:第二组的第一个元素与第一组的第一个元素进行插入排序,第三组的第一个元素与第一二组的第一个元素进行插入排序...
// 第二组的第二个元素与第一组的第二个元素进行插入排序,第三组的第二个元素与第一二组的第二个元素进行插入排序...
// ...
for (int i = x + inc; i < src.length; i += inc) {
tmp = src[i];
int j = i - inc;
for (; j >= 0 && tmp < src[j]; j-=inc) {
src[j + inc] = src[j];
}
src[j + inc] = tmp;
}
}
//不必担心增量为1时会完全等同于普通的直接插入排序,进而造成性能问题
//原因:在排序过程中匹配时遵循"小者胜,大者让"的原则,因此在搜索头进行回顾匹配时,会很快(1~2个元素左右)碰上不久前刚刚打败过自己的对手.
if(inc == 1){
break;
}
}
}
~~~
<br/>
## 3.简单选择排序
基本思想:在要排序的一组数中,选出最小的一个数与第一个位置的数交换;然后在剩下的数当中再找最小的与第二个位置的数交换,如此循环到倒数第二个数和最后一个数比较为止。
![简单选择排序][简单选择排序]
~~~java
/**
* 简单选择排序(从小到大)
* @param src
*/
public void select_sort(int[] src){
int pos,tmp,j; //定义最小值索引,最小值缓存,最小值检索起点索引
//在每个i位置放入合适的值:i==0时放最小值;i==1时放第二小的值...
for (int i = 0; i < src.length; i++) {
//假定i位置的值就是合适的值
tmp = src[i];
pos = i;
//检索i之后的所有元素(前面的元素都比i位置的小),找到一个最小的,与i位置的元素换位
for (j = i+1; j < src.length; j++) {
if(src[j] < tmp){
tmp = src[j];
pos = j;
}
}
src[pos] = src[i];
src[i] = tmp;
}
}
~~~
<br/>
## 4.堆排序
基本思想:构建一个大顶堆完全二叉树,此时整个二叉树是无序的,二叉树中的无序部分称为堆,堆顶为最大值。将堆中的末尾节点与堆顶节点互换,将刚换到末尾的节点踢出堆(刚被换到末尾的节点与之前被换到末尾的节点之间将是有序的),调整剩余节点,使其满足大顶堆的要求。以此类堆,最后堆将消失,得到一组正序排列的节点。
建堆:
![堆排序1][堆排序1]
交换,从堆中踢出最大数
![堆排序2][堆排序2]
剩余结点再建堆,再交换踢出最大数
![堆排序3][堆排序3]
~~~java
/**
* 调整堆
* @param src 存放节点的数组,节点号从1开始
* @param i 调整起点节点号
* @param size 有效节点长度
*/
private void adjHeap(int[] src,int i,int size){
int lc = 2*i;//左子节点号
int rc = 2*i + 1;//右子节点号
int mx = i;//最大值节点号
//非叶节点最大序号为size/2
if(i<=size/2){
//如果左子节点存在,且大于最大节点
if(lc<=size && src[lc-1]>src[mx-1]){
mx = lc;
}
//如果右子节点存在,且大于最大节点
if(rc<=size && src[rc-1]>src[mx-1]){
mx = rc;
}
//表示节点关系需要变化
if(mx!=i){
swap(src,mx,i);
adjHeap(src,mx,size);
}
}
}
/**
* 交换节点
* @param src 存放节点的数组,节点号从1开始
* @param p 前一个节点号
* @param n 后一个节点号
*/
private void swap(int[] src,int p, int n){
int tmp = src[p-1];
src[p-1] = src[n-1];
src[n-1] = tmp;
}
/**
* 创建初始堆
* @param src 存放节点的数组,节点号从1开始
* @param size 有效节点长度
*/
private void buildHeap(int[] src, int size){
for(int i=size/2; i>=1; i--){
adjHeap(src,i,size);
}
}
/**
* 最大堆排序(从小到大)
* @param src
*/
public void heap_sort(int[] src){
int size = src.length;
buildHeap(src,size);
for(int i=size; i>=1; i--){
swap(src,1,i);
adjHeap(src,1,i-1);
}
}
~~~
<br/>
## 5.冒泡排序
基本思想:在要排序的一组数中,对当前还未排好序的范围内的全部数,由始至终对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒。
![冒泡排序][冒泡排序]
~~~java
/**
* 冒泡排序(从小到大)
* @param src
*/
public void bubble_sort(int[] src){
int tmp;
//移动测头
for (int i = 0; i < src.length; i++) {
//从测头向前推进,若相邻元素前者大于后者,则两者换位
for (int j = i; j > 0; j--) {
if(src[j] < src[j-1]){
tmp = src[j];
src[j] = src[j-1];
src[j-1] = tmp;
}
}
}
}
~~~
<br/>
## 6.快速排序
基本思想:选择一个基准元素,通常选择第一个元素或者最后一个元素,通过一趟扫描,将待排序列分成两部分,一部分比基准元素小,一部分大于等于基准元素,此时基准元素在其排好序后的正确位置,然后再用同样的方法递归地排序划分的两部分。
![快速排序][快速排序]
~~~java
/**
* 快速排序(从小到大)
* @param src
*/
public void quick_sort(int[] src){
doSort(src,0,src.length-1);
}
/**
* @param src 数组对象
* @param bi 有效位开头
* @param ei 有效位结尾
* @return
*/
private int getMiddle(int[] src, int bi, int ei){
//缓存开头的元素作为中轴
int tmp = src[bi];
while(bi < ei){
//从右向左开始对比,不比中轴小的元素跳过,并继续向左推进
while(bi < ei && src[ei] >= tmp){
ei--;
}
//能够执行到这里表示src[ei]<tmp那么ei位元素应该被放到tmp元素左边
//但是tmp左边可能有多个元素该放在什么位置暂时未知先放在低于tmp的元素区的开头即bi位
//bi位元素已缓存为tmp不会丢失
src[bi] = src[ei];
//从左向右开始对比,不比中轴大的元素跳过,并继续向右推进
while(bi < ei && src[bi] <= tmp){
bi++;
}
//原理同上ei位元素在之前已经复制到bi位ei位已经没有意义了
src[ei] = src[bi];
}
src[bi] = tmp;
return bi;
}
/**
* @param src 数组对象
* @param bi 有效位开头
* @param ei 有效位结尾
*/
private void doSort(int[] src, int bi, int ei){
if(bi >= ei) return;
int mid = getMiddle(src,bi,ei);
doSort(src,bi,mid-1);
doSort(src,mid+1,ei);
}
~~~
<br/>
## 7.归并排序
基本思想:归并排序法是将两个以上有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列
![归并排序][归并排序]
~~~java
/**
* @param src 数组对象
* @param bi 有效位开头
* @param ei 有效位结尾
*/
private void doSort(int[] src, int bi, int ei){
if(bi >= ei) return;
int mid = (bi + ei)/2;
doSort(src, bi, mid);
doSort(src, mid + 1, ei);
merge(src, bi, mid, ei);
}
/**
* @param src 数组对象
* @param bi 有效位开头
* @param mid 有效位中点
* @param ei 有效位结尾
*/
private void merge(int[] src, int bi, int mid, int ei){
int[] tmp = new int[ei-bi+1];
int ti = 0; //tmp数组的索引
int fbi = bi; //前半段的起点
int bbi = mid + 1;//后半段的起点
while (fbi<=mid&&bbi<=ei) {
if(src[fbi] <= src[bbi]){
//更小的元素将先被放入tmp然后再对比下一个
//因为是递归方法,所以前半段是有序的,后半段也是有序的,
//所以前半段的第一个元素比后半段的第一个元素小,那么前半段的第一个元素一定是最小的
//比较大小往tmp中插入的过程其实类似选择排序+希尔排序
tmp[ti++] = src[fbi++];
}else{
tmp[ti++] = src[bbi++];
}
}
//有序元素放入tmp后,前半段的游标可能没有到头(因为后半段的可能先到头了)
while (fbi<=mid){
tmp[ti++] = src[fbi++];
}
//有序元素放入tmp后,后半段的游标可能没有到头(因为前半段的可能先到头了)
while (bbi<=ei){
tmp[ti++] = src[bbi++];
}
//用tmp中排好序的元素替换掉src中指定范围的元素
for (ti = 0; ti < tmp.length; ti++) {
src[bi+ti] = tmp[ti];
}
}
public void merge_sort(int[] src){
doSort(src,0,src.length-1);
}
~~~
<br/>
## 8.基数排序
基本思想:创建0-9十个容器,对数组中每个数取其相同位的数字,将数组元素放入到对应的容器中,然后按容器顺序从容器中取出放回数组,以此类推当位数递增到最大数的位宽时,排序完成
![基数排序][基数排序]
~~~java
public void radix_sort(int[] src){
//取最大值
int max = src[0];
for (int i = 0; i < src.length; i++) {
max = src[i] > max ? src[i] : max;
}
//取最大值的位数
int w = 0;
while (max>0){
max/=10;
w++;
}
//创建十个队列,用二维数组模拟
int[][] bkt = new int[10][src.length];
//记录每个bkt队列中有几个src元素
int[] cm = new int[10];
for (int i = 0; i < w; i++) {
//遍历元素按元素的第i位数值分类排放
for (int j = 0; j < src.length; j++) {
int item = src[j];
//求src中第j(从0开始)个元素的第i(从0开始)位数字
//例如987的第1位数字是(987%100)/10
int l = (item % (int)Math.pow(10,i+1))/(int)Math.pow(10,i);
//bkt第l个队列可能有多个数据,用现有的数量表示位置
bkt[l][cm[l]] = item;
//位置后移1位
cm[l] += 1;
}
//重新取出放回src
//记录已经取出的个数
int count = 0;
for (int j = 0; j < 10; j++) {
//第i位为j的元素的个数
int cml = cm[j];
while (cm[j] > 0){
//j从小到大递增,这样取数据将使src具有递增趋势
//将这样的元素存入bkt,将使bkt中每个队列都具有递增趋势
//为了不破换这样的趋势,将需要从每个bkt队列的开头开始取数据
src[count] = bkt[j][cml-cm[j]];
//减去一个元素
cm[j] -= 1;
count++;
}
}
}
}
~~~
[八大排序]:http://my.csdn.net/uploads/201205/12/1336792573_1570.png
[插入排序]:http://my.csdn.net/uploads/201205/12/1336792682_4218.png
[希尔排序]:http://my.csdn.net/uploads/201205/12/1336792712_3691.png
[简单选择排序]:http://my.csdn.net/uploads/201205/12/1336793705_2932.png
[堆排序1]:http://my.csdn.net/uploads/201205/12/1336793950_6973.png
[堆排序2]:http://my.csdn.net/uploads/201205/12/1336793977_2095.png
[堆排序3]:http://my.csdn.net/uploads/201205/12/1336793996_7183.png
[冒泡排序]:http://my.csdn.net/uploads/201205/12/1336807392_9769.png
[快速排序]:http://my.csdn.net/uploads/201205/12/1336807447_1275.png
[归并排序]:http://my.csdn.net/uploads/201205/12/1336807959_9188.png
[基数排序]:http://my.csdn.net/uploads/201205/12/1336807988_2428.png