mirror of
https://github.com/kaka111222333/kaka111222333.github.io.git
synced 2025-12-21 01:04:53 +08:00
434 lines
13 KiB
Markdown
434 lines
13 KiB
Markdown
---
|
||
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.希尔排序(缩小增量排序)
|
||
|
||
基本思想:算法先将要排序的一组数按某个增量d(n/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
|