在平时的开发过程中,需要用到排序的时候,都是调用java已经自带的接口。简单,高效和稳定,所以,我在这之前,都不懂得八大排序是哪八大排序。参加校园招聘后,才发现,排序的方面考得很多,于是又了这篇文章,算是一个学习记录。
1、快速排序
顾名思义,快速排序是快速的,它的时间复杂度是O(nlgn),速度那是相当的快。同时,快速排序所占用空间也很小,所以快速排序是又快同时占用的内存又少,所以是应用最广的一种排序方法。
原理:拿数列的第一个或者最后一个数作为标兵,同时设置高指针和低指针,高往低走选出一个比标兵小的值作交换,低往高走选出一个比标兵大的值作交换,两个方向交叉进行,直到指针相遇。交换完一次完成之后,以标兵的位置在进行上述操作,递归进行排序,直到递归的序列的长度为1
public class Main {
public static void main(String[] args) {
int[] a = { 30, 25, 24, 31, 28, 46, 20, 50 };
print(a);
// 0为低指针,a.length-1 为高指针
quickSort(a, 0, a.length - 1);
print(a);
}
static void quickSort(int a[], int low, int high) {
// 判断序列长度是否为1
if (low < high) {
// 如果不为1,就进行第一次快速排序,同时返回标兵最后的位置
int bb = partition(a, low, high);
// 以标兵为终点,再进行一轮排序
quickSort(a, low, bb - 1);
// 以标兵为起点,再进行一轮排序
quickSort(a, bb + 1, high);
}
}
static int partition(int a[], int low, int high){
//拿到标兵
int bb = a[low];
//判断高低指针是否相遇
while (low < high) {
//如果高低指针不相遇并且高指针所指向的值大于或者等于标兵
//这个循环执行完毕,高指针指向的,要不寻找到一个比标兵小的值,要不就找到了本身
//如果找到了本身,说明序列已经是有序的
while (low < high && a[high] >= bb){
--high;
}
swap(a, low, high);
//原理如上面解释的一样
while (low < high && a[low] <= bb)
++low;
swap(a, low, high);
}
print(a);
return low;
}
static void print(int a[]) {
for (int j = 0; j < a.length; j++) {
System.out.print(a[j] + ",");
}
System.out.println();
}
static void swap(int[] a, int low, int high) {
int tmp = a[low];
a[low] = a[high];
a[high] = tmp;
}
}
2、插入排序
原理:在待排序的序列里拿出一个数按照大小判断插入到已排序的序列里的合理位置,直到待排序的序列无值可取。
public class Main1 {
public static void main(String[] args) {
int[] a = { 5, 74, 5, 6, 0, 1, 4, 8, 3, 4, 8, 50, 30 };
for (int i = 1; i < a.length; i++) {
// 拿到标兵
int bb = a[i];
// 标兵前一个位置
int j = i - 1;
//如果标兵小于前一个位置,所以标兵是可以插入到已排好的序列里的
//如果标兵大于或者前一个位置,所以标兵无需插入
//因为标兵加入已排好的序列里,不会打乱已排好的序列
if (bb < a[j]) {
// 当标兵小于前一个
while (j >= 0 && bb < a[j]) {
// 给标兵腾出位置
a[j + 1] = a[j];
//移动标兵要插入的位置
j--;
}
//这里j需要加上1,因为当给标兵腾出位置后,又进行了一次j--,
//而进行了一次j--后,已经不符合while循环的条件,
//只有加上1,才符合while循环的条件,才是正确的位置
a[j+1] = bb;
}
}
print(a);
}
static void print(int[] a) {
for (int j = 0; j < a.length; j++) {
System.out.print(a[j] + ",");
}
System.out.println();
}
}
3、希尔排序(插入排序的增强版)
插入排序有一个很重要的特点是:如果要排序的序列是相对有序的,那么时间复杂度会相对较低。因为在标兵遍历已排好的序列的循环次数会大大减少。
为什么要有希尔排序?
原因一:如上面所说:排序的序列是相对有序的,那么插入排序时间复杂度会相对较低。
原因二:如果一个大的数往后移动,普通插入排序只能一位一位移动,而希尔排序移动的距离远。
希尔排序——分组插入排序形成相对有序的序列,在进行一次完整的插入排序。如何分组呢?通过增加增量进行分组。
例如:5, 74, 5, 6, 0, 1,序列长度为:13。先取6/2=3为分组的增量,那么5和6分到一组,74和0分到一组,5和1分到了一组。分成三组进行插入排序。
序列变成:5,0,1,6,74,5。
然后在减少增量3/2=1,现在增量为1,进行一次完全的插入排序。
好,现在来分析,为什么要这样做?用0和74来具例子。
0在序列中是最小,所以要从第5位插入到第0位,在普通的插入排序中,进行这个过程要循环五次,而现在,只要进行两次,效率大大的提升。74是最大,要换到最后一位,本来要循环五次,现在两次就搞定了。
public class Main1 {
public static void main(String[] args) {
int[] a = {5,8,10,7,6,50,8,70};
int increm = a.length/2;
while(increm >= 1){
xESort(a,increm);
increm = increm/2;
}
print(a);
}
/**
* 这个方法与插入排序的原理相同,只是插入排序的增量默认为1而已
* @param a
* @param increm
*/
private static void xESort(int[] a, int increm) {
for(int i = increm; i < a.length; i++){
int bb = a[i];
int j = i - increm;
while(j >= 0 && bb < a[j]){
//把a[j]往后移动,给标兵腾位置
a[j+increm] = a[j];
j -= increm;
}
a[j+increm] = bb;
}
}
static void print(int[] a) {
for (int j = 0; j < a.length; j++) {
if(j == a.length-1){
System.out.print(a[j]);
}else{
System.out.print(a[j] + ",");
}
}
System.out.println();
}
}
4、简单选择交换排序
简单选择交换排序的原理超级简单:就是在序列里找到最大或者最小的数,最大的与右指针做交换,最小的与左指针做交换,以此类推,直到左右指针相遇,排序就完成了!
public class Main1 {
public static void main(String[] args) {
int[] a = { 100, 50, 7, 8, 9, 10, 50, 30, 50, 70 };
for (int i = 0; i < a.length / 2; i++) {
int[] position = getMinAndMaxPosition(a, i);
if (i != position[0]) {
swap(a, position[0], i);
}
if (i != position[1]) {
swap(a, position[1], a.length - i - 1);
}else{
//这里需要特别注意,如果i刚好是最大的那个值,
//进过上面交换后,最大的值已经跑到position[0]上面了
swap(a, position[0], a.length - i - 1);
}
}
print(a);
}
static void print(int[] a) {
for (int j = 0; j < a.length; j++) {
if (j == a.length - 1) {
System.out.print(a[j]);
} else {
System.out.print(a[j] + ",");
}
}
System.out.println();
}
/**
* 0是最小 1是最大
*
* @param a
* @param position
* @return
*/
private static int[] getMinAndMaxPosition(int[] a, int position) {
int[] b = new int[2];
int max = Integer.MAX_VALUE;
int min = Integer.MIN_VALUE;
for (int i = position; i < a.length - position; i++) {
if (max > a[i]) {// 找最小
b[0] = i;
max = a[i];
}
if (min < a[i]) {// 找最大
b[1] = i;
min = a[i];
}
}
return b;
}
static void swap(int[] a, int low, int hight) {
int tmp = a[low];
a[low] = a[hight];
a[hight] = tmp;
}
}
5、冒泡排序
冒泡排序的原理与选择插入排序的原理有异曲同工之处。冒泡排序的原理为:遍历每一个数据,让数据与相邻的数据做比较,如果符合规则(大于或者小于),则做交换(冒泡)。每一个数据都这样去比较,最后的序列就变成有序的了。这应该是最简单的一种排序算法。
public class Main {
public static void main(String[] args) {
int[] a = {5,8,9,4,2,1,10,5,6,3,7};
bubling(a);
print(a);
}
static void bubling(int[] a){
for(int i = 0; i < a.length;i++){
for(int j = 0; j <a.length;j++){
if(a[i] > a[j]){
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
}
}
static void print(int[] a) {
for (int j = 0; j <a.length; j++) {
System.out.print(a[j] + ",");
}
System.out.println();
}
}
6、归并排序
归并排序非常的有趣:感觉没有干什么事,就把问题解决了!每一个层级只要处理本层级的事情,一层一层递进,到了最上层,所有问题就给解决了。而归并排序最让人感动的是:每一层除了数量的差别有所不同之外,具体实行方案是相同的!所以,很简单啦。
public class Main {
public static void main(String[] args) {
int[] a = {50,8,7,9,50,407,60,8,24,25,63,74};
mergeSort(a,0,a.length-1);
print(a);
}
private static void mergeSort(int[] a, int low, int hight) {
//递归出口,当数值只有一个人时,就不需要继续分治
if(low < hight){
//继续分治的边界
int mid = (hight+low)/2;
//最低到新求出来的边界,分治
mergeSort(a, low, mid);
//边界到最高,分治
//(需要注意的一点:mid一定要加上1,这是一种“态度”,代表计算在向高处靠拢,
//low <hight到了某个程度一定会成立,不然有可能出现递归无出口,导致栈溢出)
mergeSort(a, mid+1, hight);
//具体治理的实现
merge(a,low,hight,mid);
}
}
private static void merge(int[] a, int low, int hight, int mid) {
//用来储存分治成功的临时数组
int[] temp = new int[hight-low + 1];
int k = 0;
//建立左右指针
int left = low;
int right = mid + 1;
//这里需要理解,下属已经把各自的序列治理得有序,此时只要比较左右指针指向值得大小
while(left <= mid && right <= hight){
if(a[left] <= a[right]){
temp[k++] = a[left++];
}else{
temp[k++] = a[right++];
}
}
//左边剩下,右边一定为空,所以放心大胆放入数组
while(left <= mid){
temp[k++] = a[left++];
}
//右边剩下,左边一定为空,所以放心大胆放入数组
while(right <= hight){
temp[k++] = a[right++];
}
//覆盖原来数组
for(int i = 0; i < temp.length;i++){
a[i + low] = temp[i];
}
}
static void print(int[] a) {
for (int j = 0; j < a.length; j++) {
if(j == a.length-1){
System.out.print(a[j]);
}else{
System.out.print(a[j] + ",");
}
}
System.out.println();
}
}
7、堆排序
堆排序要理解相对比较难,但是理解了之后,也就感觉很简单了。堆是一种数据结构,根节点最大或者最小,然后各自的子树叶符合根节点最大或者最小。
步骤:第一步:把树进行堆化。第二步:输出根节点,然后重新进行堆化。
public class Main1 {
public static void main(String[] args) {
int[] a = {5,7,80,90,4,5,0,4,2,36,47,54,62};
arrayHeap(a);
for(int i = 1; i <= a.length;i++){
printfHeap(a,i);
}
}
private static void printfHeap(int[] a,int n) {
System.out.print(a[0] + ",");
//输出某一个节点之后,进行交换,然后不再理睬输出后的节点
swap(a,0,a.length-n);
//对根节点进行堆化
heap(a,0,a.length-n);
}
static void swap(int[] a, int low,int hight){
int tmp = a[low];
a[low] = a[hight];
a[hight] = tmp;
}
private static void arrayHeap(int[] a) {
//从最后一颗树的节点进行堆化
for(int i = (a.length/2)+1; i >=0; i--){
heap(a,i,a.length);
}
}
/**
* 针对某个节点进行堆处理
* 这里其实就是插入排序,只是插入的路径是沿着树的某条路径而已
*/
private static void heap(int[] a, int i,int n) {
//拿到标兵
int bb = a[i];
//左子树的下标
int j = 2*i+1;
//当标兵大于左子树或者大于右子树
while((j < n && bb > a[j]) || (j+1 < n && bb > a[j+1])){
//判断左右子树哪个更小
if(j+1 < n && a[j+1] < a[j]){
//右子树更小,则交换右子树
j++;
}
//标兵的位置,永远是被填的(赋值)
a[i] = a[j];
i = j;
j = 2*i+1;
}
a[i] = bb;
}
}
8、基数排序
暂定。。。。这个排序没有时间去看了,后面找时间不补上来。
下下个星期,十道面试编程题的详解。