排序分为内部排序和外部排序
内部排序:数据在内存中进行排序
外部排序:因为排序的数据较大,一次不能容纳全部的数据,在排序过程中需要访问外存
这里的八大排序算法全是内部排序
当n较大时,应该采用时间复杂度为O(nlog2n)的排序算法:快速排序、堆排序和归并排序
快速排序:是目前基于比较的内部排序中最好的排序方法,当待排序的关键字是随机分布时,快速排序的平均时间最短
1.插入排序——直接插入排序
基本思想:
将一个数据插入到已排序好的数据队列中,从而得到一个新的、数据量加1的数据队列。
即:先将序列的第一个数据当成一个有序的序列,然后从第二个数据开始逐个进行插入,直至整个序列有序为止
直接插入排序示例:
如果插入数据时碰到相等的数据,那么把要插入的数据放到相等数据的后面
算法实现:
package 直接插入排序算法;
/**
* 基本思想
* 将一个数据插入到已排序好的数据队列中,从而得到一个新的、数据量加1的数据队列。
* 即:先将序列的第一个数据当成一个有序的序列,然后从第二个数据开始逐个进行插入,直至整个序列有序为止
*/
public class StraightInsertSort {
public static void print(int[] a, int i) {
System.out.print(i + ":");
for(int j = 0; j < a.length; j++) {
System.out.print(a[j] + " ");
}
System.out.println();
}
public static void straightInsertSort(int[] a) {
for(int i = 1; i < a.length; i++) {
// 前面的序列已经从小到大排好序了
// 如果比前面序列的最后一个数字小,则找到位置后插入
// 如果比前面序列的最后一个数字大,则直接插入
if(a[i] < a[i - 1]) {
int x = a[i]; // 要插入的数据
int j = i - 1;
while(j >= 0 && a[j] > x) {
a[j + 1] = a[j];
j--;
}
a[j + 1] = x;
}
// 打印每次排序的结果
print(a, i);
}
}
public static void main(String[] args) {
int[] a = new int[] {3, 1, 5, 7, 2, 4, 9, 6};
straightInsertSort(a);
}
}
基本思想:
先将整个待排序的序列分割成若干个子序列,分别进行直接插入排序,待整个序列中的数据“基本有序”时,再对全体数据进行直接插入排序
即:先将要排序的序列按某个增量d(n/2,n为要排序的个数)分成若干组子序列,每组序列中相邻数据下标相差d,对每组序列分别进行直接插入排序,然后再用d/2作为增量重复以上操作,直到增量为1时进行分组直接插入排序(当增量为1时,其实就是将所有数据分为1组并进行直接插入排序。)
算法实现:
package 希尔排序算法;
public class ShellInsertSort {
public static void print(int[] a, int i) {
System.out.print(i + ":");
for(int j = 0; j < a.length; j++) {
System.out.print(a[j] + " ");
}
System.out.println();
}
public static void shellInsertSort(int[] a, int dk) {
for(int i = dk; i < a.length; i++) {
// 前面的序列已经从小到大排好序了
// 如果比前面序列的最后一个数字小,则找到位置后插入
// 如果比前面序列的最后一个数字大,则直接插入
if(a[i] < a[i - dk]) {
int x = a[i]; // 要插入的数据
int j = i - dk;
while(j >= 0 && a[j] > x) {
a[j + dk] = a[j];
j -= dk;
}
a[j + dk] = x;
}
// 打印每次排序的结果
print(a, i);
}
}
public static void shellSort(int[] a) {
int dk = a.length / 2;
while(dk >= 1) {
shellInsertSort(a, dk);
dk = dk / 2;
}
}
public static void main(String[] args) {
int[] a = new int[] {3, 1, 5, 7, 2, 4, 9, 6};
shellSort(a);
}
}
3.选择排序——简单选择排序
基本思想:
在要排序的一组数据中,选出最小的数与第1个位置的数进行交换,然后在剩下的数中选择最小的数再找最小的数与第2个位置的数进行交换,依次类推,直到第n-1个数与第n个数进行了比较为止
简单选择排序的示例:
算法实现:
package 选择排序算法;
public class SelectSort {
public static void print(int[] a, int i) {
System.out.print("第" + (i + 1) + "趟:");
for(int j = 0; j < a.length; j++) {
System.out.print(a[j] + " ");
}
System.out.println();
}
/**
* @param a 数组
* @param i 从下标为i这个数开始(包括)
* @return 返回最小的数的下标
*/
public static int selectMinKey(int[] a, int i) {
int k = i;
for(int j = i + 1; j < a.length; j++) {
if(a[j] < a[k]) {
k = j;
}
}
return k;
}
public static void selectSort(int[] a) {
int key = 0;
int temp = 0;
for(int i = 0; i < a.length - 1; i++) {
key = selectMinKey(a, i);
if(key != i) {
temp = a[key];
a[key] = a[i];
a[i] = temp;
}
print(a, i);
}
}
public static void main(String[] args) {
int[] a = new int[] {3, 1, 5, 7, 2, 4, 9, 6};
selectSort(a);
}
}
堆的定义如下:具有n个元素的序列(k1,k2........kn),当且仅当满足
时称之为堆。
k1 <= k2 且 k1 <= k3
k2 <= k4 且 k2 <= k5
k3 <= k6 且 k3 <= k7
若以一维数组存储一个堆,则堆对应一棵完全二叉树
根节点(堆顶元素)的值必定是最大(最小)的
所有非叶节点的值均大于等于(小于等于)其子女的值
堆排序:
初始时把要排序的n个数的序列看作是一棵顺序存储的二叉树(一维数组存储二叉树),当不一定是堆,调整他们的存储序,使之成为一个堆。将堆顶元素输出,得到n个数中最小(最大)的数。然后调整剩下的n-1个数,使之成为堆,输出堆顶元素,得到n个数中次小(次大)的数。依次类推,最后得到n个数的有序序列。
因此,实现堆排序需解决两个问题:
1.如何将n个待排序的数建成堆
2.输出堆顶元素后,怎么调整剩余的元素,使他们成为一个新堆
先解决第二个问题:
如图是调整小顶堆的方法
主要是:将根节点与左、右子树中较小元素进行交换
对n个元素初始建堆的过程:
对初始序列建堆得过程,其实就是一个反复筛选的过程
算法实现:
package 堆排序算法;
public class HeapSort {
public static int[] heapSort(int[] A, int n) {
//堆排序算法
int i;
//先把A[]数组构建成一个大顶堆。
//从完全二叉树的最下层最右边的非终端结点开始构建。
for(i=n/2-1;i>=0;i--){
HeapAdjust(A,i,n);
}
//开始遍历
for(i=n-1;i>0;i--){
swap(A,0,i);
//每交换一次得到一个最大值然后丢弃
HeapAdjust(A,0,i);
}
return A;
}
//A[i]代表的是下标为i的根结点
private static void HeapAdjust(int[] A,int i,int n){
//【注意】这里下标从0开始
int temp;
//存储根结点
temp = A[i];
//沿根结点的左右孩子中较大的往下遍历,由于完全二叉树特性 i的左子节点2i+1 i的右子节点2i+2
for(int j=2*i+1;j=A[j]){
break;
}
//将子节点赋值给根结点
A[i] = A[j];
//将子节点下标赋给i
i = j;
}
//将存储的根结点的值赋给子节点
A[i] = temp;
}
private static void swap(int[] A,int i,int j){
int temp = A[i];
A[i]=A[j];
A[j] = temp;
}
public static void main(String[] args) {
int[] a = new int[] {1,3,4,7,2,5,9,6};
int[] b = heapSort(a, a.length);
for(int i = 0; i < b.length; i++) {
System.out.print(b[i] + " ");
}
}
}
5.交换排序——冒泡排序
基本思想:
在要排序的一组数中,对当前还未排好序的范围内的全部数,自上而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的数往上冒。即:每当两相邻的数比较后发现他们的排序与排序要求相反时,就将他们互换。
冒泡排序示例:
算法实现:
package 冒泡排序;
/**
* 从小到大排序
*/
public class BubbleSort {
public static void bubbleSort(int[] a) {
// i表示第几趟排序 排序(a.length - 1)趟即可
for(int i = 0; i < a.length - 1; i++) {
// j表示数据的下标
for(int j = 0; j < a.length - i - 1; j++) {
if(a[j] > a[j + 1]) {
int temp = a[j];
a[j] = a[j + 1];
a[j + 1] = temp;
}
}
}
}
public static void main(String[] args) {
int[] a = new int[] {9, 6, 7, 9, 2, 3};
bubbleSort(a);
for(int i = 0; i < a.length; i++) {
System.out.print(a[i] + " ");
}
}
}
基本思想:
(1)选择一个基准数据,通常选择第一个数据或者最后一个数据
(2)通过一趟排序,将带排序的序列分为两部分,其中一部分数据比基准数据小,另一部分数据比基准数据大。
(3)此时基准数据排在正确的位置
(4)然后对这两部分记录用同样的方法进行排序,直至整个序列有序。
快速排序的示例:
左边的比基准数据小,右边的比基准数据大
27比49小,交换27和49的位置
65比49大,交换65和49的位置
算法实现:
package 快速排序;
public class QuickSort {
public static int[] quickSort(int[] A, int n) {
//快速排序
qSort(A,0,n-1);
return A;
}
public static void qSort(int[] A,int left,int right){
//枢轴
int pivot;
if(left=pivotKey){
right--;
}
//用替换方式,因为枢轴给备份了,多出一个存储空间
A[left]=A[right];
while(leftA[right]){
swap(A,left,right);
}
if(A[mid]>A[left]){
swap(A,mid,left);
}
if(A[mid]>A[right]){
swap(A,mid,right);
}
return A[left];
}
public static void swap(int[] A,int i,int j){
int temp =A[i];
A[i]=A[j];
A[j]=temp;
}
public static void main(String[] args) {
int[] a = new int[] {1, 7, 6, 4, 9};
int[] b = quickSort(a, a.length);
for(int i = 0; i < b.length; i++) {
System.out.print(b[i] + " ");
}
}
}
7.归并排序
基本思想:
把待排序序列分为若干个子序列,每个子序列是有序的,然后再把有序子序列合并为整体有序序列。
归并排序示例:
算法实现:
package 归并排序;
public class MergeSort {
public int[] mergeSort(int[] A, int n) {
//归并排序,递归做法,分而治之
mSort(A,0,n-1);
return A;
}
private void mSort(int[] A,int left,int right){
//分而治之,递归常用的思想,跳出递归的条件
if(left>=right){
return;
}
//中点
int mid = (left+right)/2;
//有点类似后序遍历!
mSort(A,left,mid);
mSort(A,mid+1,right);
merge(A,left,mid,right);
}
//将左右俩组的按序子序列排列成按序序列
private void merge(int[] A,int left,int mid,int rightEnd){
//充当tem数组的下标
int record = left;
//最后复制数组时使用
int record2 = left;
//右子序列的开始下标
int m =mid+1;
int[] tem = new int[A.length];
//只要left>mid或是m>rightEnd,就跳出循环
while(left<=mid&&m<=rightEnd){
if(A[left]<=A[m]){
tem[record++]=A[left++];
}else{
tem[record++]=A[m++];
}
}
while(left<=mid){
tem[record++]=A[left++];
}
while(m<=rightEnd){
tem[record++]=A[m++];
}
//复制数组
for( ;record2<=rightEnd;record2++){
A[record2] = tem[record2];
}
}
public static void main(String[] args) {
int[] a = new int[] {1, 5, 3, 7, 5};
int[] b = new MergeSort().mergeSort(a, a.length);
for(int i = 0; i < b.length; i++) {
System.out.print(b[i] + " ");
}
}
}
8.基数排序
基本思想:
先按照低位排序,然后收集;然后再按照高位排序,然后收集;依次类推,直到最高位。
算法实现:
package 基数排序;
import java.util.ArrayList;
public class RadixSort {
public int[] radixSort(int[] A, int n) {
//基于桶排序的基数排序
//确定排序的趟数,即排序数组中最大值为809时,趟数为3
int max=A[0];
for(int i=0;imax){
max= A[i];
}
}
//算出max的位数
int time=0;
while(max>0){
max/=10;
time++;
}
//【桶】初始化十个链表作为桶,用户分配时暂存
ArrayList> list = new ArrayList>();
for(int i=0;i<10;i++){
ArrayList Item = new ArrayList();
list.add(Item);
}
//进行time次分配和收集
for(int i=0;i