一、排序
1.什么是排序
排序是将一堆杂乱无章的数据,按某个关键字重新排列成有序的序列。排序分为稳定排序和不稳定排序:
-
稳定排序:如果在待排序的序列中存在多个具有相同关键字的元素,在使用某种排序后,这些相同的关键字的元素在序列中的相对次序未发生改变,则称这种排序方法是稳定的,常见的稳定排序有冒泡排序、插入排序、基数排序,归并排序等。
-
不稳定排序:与稳定排序相反,在使用某种排序后,这些相同关键字的元素在序列中的次序发生了改变,则是不稳定排序,常见的额不稳定排序有选择排序、希尔排序、快速排序等。
例子:排序前有(22,
33,12,57,
33,98,16,
33);若经过排序后得到的是(12,16,22,
33,
33,
33,57,96),则这种排序是稳定排序;若得到的是(12,16,22,
33,
33,
33,57,96)就是不稳定排序。
排序还可以分为比较排序和非比较排序,其中大部分的排序都是比较排序,即通过比较关键字来判断大小,作为其排序的依据。但也有不需要通过比较的排序方法,如基数排序,计数排序等。下面是常见排序的分类图:
![54061e24f8d221e9dafd80b47999a2515d9.jpg](https://oscimg.oschina.net/oscnet/54061e24f8d221e9dafd80b47999a2515d9.jpg)
常见排序方法的比较:
![acab46f32f10de2ac56df1a88c2f0196e89.jpg](https://oscimg.oschina.net/oscnet/acab46f32f10de2ac56df1a88c2f0196e89.jpg)
可以看出时间复杂度最高的就是三种基本排序:(简单)插入排序,(简单)选择排序,冒泡排序。
二、交换排序
1.冒泡排序
交换排序就是根据关键值的比较结果来交换这两个关键字在序列中的位置。常见的交换排序有冒泡排序和快速排序。
冒泡排序是一种简单的交换排序算法。它通过不断的遍历整个序列,每次比较两个元素,如果满足要求就交换位置,不满足就不交换。每遍历一趟序列找出一个最大(或最小)的元素,直到没有可以在交换的元素为止。此时表明排序完成。其一般步骤为(以升序为例):
-
比较相邻的两个元素。若第一个元素比第二个大,就将这两个元素的位置交换。
-
对序列中的所有相邻元素都执行步骤1的工作,这样就末尾元素就是该序列中最大的元素。
-
重复以上2个步骤,每次都能使一个元素有序,直到所有元素都有序为止。
冒泡排序的实现:
/**
* 冒泡排序的简单实现
* Created by bzhang on 2019/3/7.
*/
public class Sorted {
public static void main(String[] args) {
int[] data = {1,8,6,4,12,9,78,13};
bubbleSort(data);
for (int i=0;i<data.length;i++){
System.out.println(data[i]);
}
}
public static void bubbleSort(int[] data){
int len = data.length;
int t = 0;
for (int i=0;i<len-1;i++){
for (int j=0;j<len-1-i;j++){
if (data[j]>data[j+1]){
t = data[j];
data[j] = data[j+1];
data[j+1] = t;
}
}
}
}
}
2.快速排序
快速排序是冒泡排序的改进版,我们知道冒泡排序的比较交换是在相邻单元中进行的,每次只能比较一对元素,造成总的比较次数和移动次数较多。为了提高效率,快速排序便孕育而生,快速排序采用分治法:即先从序列中选取一个基准数;然后将比基准数大的放到基准数的右边,小的放到左边;再将左右区间看做两个序列重复之前的操作,直到各个区间只有一个数为止。
/**
* 排序的简单实现
* Created by bzhang on 2019/3/7.
*/
public class Sorted {
public static void main(String[] args) {
int[] data = {100,1,8,6,4,12,9,78,13,50,16,24};
fastSort(data);
for (int i=0;i<data.length;i++){
System.out.println(data[i]);
}
}
//快速排序的简单实现
public static void fastSort(int[] data){
int start = 0;
int end =data.length-1;
fastSort(data,start,end);
}
//递归实现快速排序
private static void fastSort(int[] data, int start, int end) {
if ((end-start)<1){
return;
}
int basic = data[start];
int index = start;
int over = end;
int tmp = 0;
for (int i = start+1;i<=end;i++){
if (basic>data[i]){
data[index] = data[i];
data[i] = basic;
index++;
}else {
tmp = data[over];
data[over] = data[i];
data[i] = tmp;
over--;
}
}
fastSort(data,start,index-1);
fastSort(data,index+1,end);
}
}
三、插入排序
1.简单插入排序
插入排序是一种直观的排序算法,它是通过选取一个数,寻找其在一个有序序列中的位置,对其进行插入操作从而得到一个新的有序序列。
示例:已知一个有序序列{3,6,9,11,13,17},若有元素10要插入该序列,则其插入排序步骤为:
-
比较查找10在原序列中的位置(大于9,小于11),为索引3的位置(原11的位置)。
-
将11的位置空出,并放入10。
/**
* 排序的简单实现
* Created by bzhang on 2019/3/7.
*/
public class Sorted {
public static void main(String[] args) {
int[] data = {100,1,8,6,4,12,9,78,13,50,16,24};
insertSort(data);
for (int i=0;i<data.length;i++){
System.out.println(data[i]);
}
}
//插入排序的简单实现
public static void insertSort(int[] data){
int len = data.length;
boolean flag = false; //标志位,false执行比较语句,true执行移动语句
int tmp = 0;
for (int i = 1;i<len;i++){
for (int j = 0;j<=i;j++){
//查找元素位置,查找到后,将待插入位置的元素往后移动1位
if (flag){
int t = data[j];
data[j] = tmp;
tmp = t;
}else {
if (data[i]<data[j]){
tmp = data[j];
data[j] = data[i];
flag = true;
}
}
}
flag = false;
}
}
}
2.希尔排序
希尔排序是简单插入排序的改进版,简单插入排序是挨个选取待插入元素来与一个有序序列比较查找出待插入位置,希尔排序则是将带插入序列以k(k为小于序列长度的正整数)为间隔分成若干个子序列后执行简单插入排序,然后缩小k值,循环执行前面步骤,当k=1时,序列便有序。
示例:对{3,16,22,14,8,6,17,33,41,26,11,43,38,24}使用希尔排序,间隔为4。
-
将序列以间隔4分为4个子序列,{3,8,41,38};{16,6,26,24};{22,17,11};{14,33,43}(这里是为了方便查看分组,实际并没有分成4个序列,所有元素还是在一个序列之中)。分别使用简单插入排序得到{3,8,38,41};{6,16,24,26};{11,17,22,};{14,33,43},即得到{3,6,11,14,8,16,17,33,38,24,22,43,41,26}这样一个序列。
-
再将1中得到的序列以间隔k=3进行简单插入排序,直到k=1时的排序完成,就得到一个有序的序列。
/**
* 排序的简单实现
* Created by bzhang on 2019/3/7.
*/
public class Sorted {
public static void main(String[] args) {
int[] data = {100,1,8,6,4,12,9,78,13,50,16,24};
shellSort(data,4);
for (int i=0;i<data.length;i++){
System.out.println(data[i]);
}
}
//希尔排序
public static void shellSort(int[] data,int k){
int len = data.length;
boolean flag = false; //标志位,false执行比较语句,true执行移动语句
int tmp = 0;
while (k>=1){
for (int i = k;i<len;i++){
for (int j = i%k;j<=i;j+=k){
//查找元素位置,查找到后,将待插入位置的元素往后移动k位
if (flag){
int t = data[j];
data[j] = tmp;
tmp = t;
}else {
if (data[i]<data[j]){
tmp = data[j];
data[j] = data[i];
flag = true;
}
}
}
flag = false;
}
k=k/2;
}
}
}
四、选择排序
1.简单选择排序
选择排序也是一种简单直观的排序算法,其原理是在待排序的序列中找出最大(或最小)的元素,存放到序列的起始位置,在从剩余的无序序列中找出最大(或最小)的元素,排到有序序列的下个位置。重复前面步骤,直到排序完成。
/**
* 排序的简单实现
* Created by bzhang on 2019/3/7.
*/
public class Sorted {
public static void main(String[] args) {
int[] data = {100,1,8,6,4,12,9,78,13,50,16,24,99,45,62,21,39};
selectedSort(data);
for (int i=0;i<data.length;i++){
System.out.println(data[i]);
}
}
//简单选择排序实现
public static void selectedSort(int[] data){
int len = data.length;
int max = 0;
int index = 0;
for (int i=0;i<len;i++){
index = i;
max = data[i];
for (int j = i+1;j<len;j++){
if (max>data[j]){
max = data[j];
index = j;
}
}
data[index] = data[i];
data[i] = max;
}
}
}
2.堆排序
堆排序利用的是堆的性质来实现排序,什么是堆呢?堆其实就是完全二叉树,堆又分为大根堆(最大堆)和小根堆(最小堆):
-
大根堆就是每个父节点的数据大于子节点中的数据。
-
小根堆则相反,每个父节点的数据小于子节点。
![8be1632f6d33bd471ced7b0d7a9118e99b9.jpg](https://oscimg.oschina.net/oscnet/8be1632f6d33bd471ced7b0d7a9118e99b9.jpg)
![e89501db93bd9b542402a4f8c4ffabad7df.jpg](https://oscimg.oschina.net/oscnet/e89501db93bd9b542402a4f8c4ffabad7df.jpg)
堆排序就是利用堆的特性来实现排序:每次将堆顶元素(即最大值或最小值),放在序列的最后面,然后再将剩余元素重新调整为大根堆(或小根堆),再取堆顶元素,以此类推,最终使序列有序。
/**
* 排序的简单实现
* Created by bzhang on 2019/3/7.
*/
public class Sorted {
public static void main(String[] args) {
int[] data = {100,1,8,6,4,12,9,78,13};
heapSort(data);
for (int i=0;i<data.length;i++){
System.out.println(data[i]);
}
}
//堆排序的简单实现
public static void heapSort(int[] data){
int len = data.length;
int temp = 0;
for (int i=len-1;i>=0;i--){
buildHeap(data,i+1);
temp = data[i];
data[i] = data[0];
data[0] = temp;
}
}
//建立大根堆
private static void buildHeap(int[] data, int len) {
for (int i=len-1;i>0;i--){
int temp = 0;
if (data[i]>data[(i-1)/2]){
temp = data[i];
data[i] = data[(i-1)/2];
data[(i-1)/2] = temp;
}
}
}
}
五、归并排序
1.归并排序
归并排序也是分治法的一个运用,它是通过先将分为n个(n为2就是二路归并,n>2则是多路归并)序列,再将每个子序列继续分为n个子序列的子序列,如此反复划分,直到每个序列中的元素<=m个(m>=2),是每个序列有序,在合并每个子序列。得到最终有序的序列。
![af2ff4477a4f08dd28c4f2dd46919bcecf8.jpg](https://oscimg.oschina.net/oscnet/af2ff4477a4f08dd28c4f2dd46919bcecf8.jpg)
/**
* 排序的简单实现
* Created by bzhang on 2019/3/7.
*/
public class Sorted {
public static void main(String[] args) {
int[] data = {100,1,8,6,4,12,9,78,13,101,102,103,104,105,110};
mergeSort(data);
for (int i=0;i<data.length;i++){
System.out.println(data[i]);
}
}
//归并排序的简单实现
public static void mergeSort(int[] data){
int high = data.length-1;
int low = 0;
int mid = (high+low)/2;
forkAndMerge(data,low,mid,high);
}
//先分支成只有1或2个元素的序列进行排序,后在合并成一个序列
private static void forkAndMerge(int[] data, int low, int mid, int high) {
if (high-low<=1){
if (data[high]<data[low]){
int temp = data[high];
data[high] =data[low];
data[low] = temp;
}
return;
}
forkAndMerge(data,low,(low+mid)/2,mid);
forkAndMerge(data,mid+1,(mid+1+high)/2,high);
merge(data,low,mid,high);
}
//合并两个有序的子序列
private static void merge(int[] data, int low, int mid, int high) {
int len = high-low+1;
int[] arrTemp = new int[len];
int s = 0;
for (int i=low,j=mid+1;i<=mid||j<=high;){
if (i>mid&&j<=high){
arrTemp[s++] = data[j];
j++;
}else if (i<=mid&&j>high){
arrTemp[s++] = data[i];
i++;
}else {
if (data[i]<=data[j]){
arrTemp[s++] = data[i];
i++;
}else {
arrTemp[s++] = data[j];
j++;
}
}
}
for (int i = 0;i<len;i++){
data[low+i] = arrTemp[i];
}
}
}
六、非比较排序
1.计数排序
计数排序不是基于比较的排序算法,它是一种快捷稳定的排序方法,
计数排序对一定量的整数排序时候的速度非常快,一般快于其他排序算法。但计数排序
局限性比较大
,只限于对整数进行排序,且局限在元素比较集中的情况。
计数排序的基本原理是:找出待排序序列data的最大值max和最小值min,建立一个长度为max-min+1的数组temp,temp中存放data中min到max之间所有数在数组中的个数,最后将temp中存放的各个元素的数量按索引挨个填充会data中。
/**
* 排序的简单实现
* Created by bzhang on 2019/3/7.
*/
public class Sorted {
public static void main(String[] args) {
int[] data = {6,3,8,22,33,16,18,24};
countSort(data);
for (int i=0;i<data.length;i++){
System.out.println(data[i]);
}
}
//计数排序的简单实现
public static void countSort(int[] data){
int max = getMax(data);
int min = getMin(data);
int[] temp = new int[max-min+1];
//统计min到max之间所有整数在data中出现的次数,存于temp数组中
for (int i=0;i<data.length;i++){
int t = data[i];
temp[t-min]+=1;
}
int s = 0;
//依次将temp中存的各个数放到data中。
for (int i=0;i<temp.length;i++){
int k = temp[i];
while (k>0){
data[s++] = min+i;
k--;
}
}
}
private static int getMax(int[] data) {
int max = data[0];
for (int i=1;i<data.length;i++){
if (max<data[i]){
max = data[i];
}
}
return max;
}
private static int getMin(int[] data) {
int min = data[0];
for (int i=1;i<data.length;i++){
if (min>data[i]){
min = data[i];
}
}
return min;
}
}
2.桶排序
桶排序是计数排序的升级版,
但桶排序要求数据的分布必须均匀,否则可能导致数据都集中到一个桶中。
桶排序的基本原理:找出待排序序列的上下边界(最大值max和最小值min),将其划分为n个大小相同的子区间,各个子区间各自排序,最后合并。
/**
* 排序的简单实现
* Created by bzhang on 2019/3/7.
*/
public class Sorted {
public static void main(String[] args) {
int[] data = {998,1998,2846,1694,4536,1766,8848,3369,6671};
bucketSort(data);
for (int i=0;i<data.length;i++){
System.out.println(data[i]);
}
}
//桶排序的简单实现
public static void bucketSort(int[] data){
int max = getMax(data);
int min = getMin(data);
int len = data.length;
int bucketSize = (max-min)/data.length+1; //桶的数量
List<List<Integer>> buckets = new ArrayList<>(bucketSize);
List<Integer> res = new ArrayList<>();
//创建bucketSize个桶
for(int i=0;i<bucketSize;i++){
buckets.add(new ArrayList<>());
}
for(int i=0;i<len;i++){
int index = (data[i]-min)/len; //放到哪个桶中
buckets.get(index).add(data[i]);
}
for(int i=0;i<bucketSize;i++){
//对各个桶进行排序,可以使用任意一种排序方法,这里直接使用Collections工具类的排序方法
Collections.sort(buckets.get(i));
res.addAll(buckets.get(i));
}
int s = 0;
for (int t:res) {
data[s++] = t;
}
}
private static int getMax(int[] data) {
int max = data[0];
for (int i=1;i<data.length;i++){
if (max<data[i]){
max = data[i];
}
}
return max;
}
private static int getMin(int[] data) {
int min = data[0];
for (int i=1;i<data.length;i++){
if (min>data[i]){
min = data[i];
}
}
return min;
}
}
2.基数排序
基数排序的思想是按位比较,从最低位(个位)开始,比较后得到一个新序列,在新序列的基础上在比较次低位(十位),如此反复比较到最高位,即可得到一个有序的序列。
![878f86547a76c5ca98896cf3b886d24139b.jpg](https://oscimg.oschina.net/oscnet/878f86547a76c5ca98896cf3b886d24139b.jpg)
/**
* 排序的简单实现
* Created by bzhang on 2019/3/7.
*/
public class Sorted {
public static void main(String[] args) {
int[] data = {998,19,2846,1694,536,1766,8848,9,6671};
bucketSort(data);
for (int i=0;i<data.length;i++){
System.out.println(data[i]);
}
}
//基数排序的简单实现
public static void radixSort(int[] data){
int max = getMax(data);
int num = getDigits(max); //计算序列中最大数的位数
int m = 1;
for (int i=0;i<=num;i++){
m*=10; //用于取各个位上的值,依次为10,100,1000。。。
radixSort(data,m);
}
}
//根据各位置的大小进行排序,依次为个位,十位,百位,千位。。。
private static void radixSort(int[] data, int m) {
int temp = 0;
for (int i= 0; i<data.length-2;i++){
if ((data[i]%m)/(m/10)>(data[i+1]%m)/(m/10)){
temp = data[i];
data[i] = data[i+1];
data[i+1] = temp;
}
}
}
private static int getDigits(int max) {
int num = 0;
for (int i=1;;i*=10){
if (max/i>0){
num++;
}else {
return num;
}
}
}
private static int getMax(int[] data) {
int max = data[0];
for (int i=1;i<data.length;i++){
if (max<data[i]){
max = data[i];
}
}
return max;
}
}