目录
一、基本概念
(1)算法的稳定性
(2)排序的分类
(3)算法的性能
内部排序算法的性能取决于算法的时间复杂度和空间复杂度,而时间复杂度一般是由比较和移动的次数决定的。
二、插入排序
插入排序的基本操作是在一个有序表中进行查找和插入。
(1)直接插入排序 / InsertSort
基本操作:将一个记录插入到已排好序的有序表中。从而得到一个新的、记录数增1的有序表。
基本思想:假设在排序过程中,待排序表L[1…n]在某次排序过程中的某一时刻状态如下:
要将元素L(i)插入到已有序的子序列L[ 1…i-1 ],需要执行以下操作,其中L[ ]表示一个表,L()表示一个元素:
1)查找出L(i)在L [ 1 … i - 1 ]中插入位置k。
2)将L [ k … i - 1 ]中的所有元素依次后移一个位置。
3)将L(i)复制到L(k)。
为了实现对L[1 … n]的排序,可以将L(2)~L(n)依次插入前面已排好序的子列表
初始L[ 1 ]视为一个已经排好序的子序列。
示例:
代码实现:
public class InsertSort {
//输出数组中的元素
public static void print(int[] arr) {
//这里一定要是static
System.out.print("将数组中的元素从小到大进行排序:");
for (int i = 0;i < arr.length;i++)
System.out.print(arr[i] + " ");
}
//对数组中的元素进行 直接插入排序
public static void sort(int[] arr) {
//这里一定要是static
if (arr.length >= 2) {
//数组长度为1时,没有必要进行排序
//不采用哨兵,数组中元素从0位置开始存放,如果采用哨兵,数组中元素从1位置开始存放,则arr[0]为哨兵
for (int i = 1; i < arr.length; i++) {
//从数组的第二个元素开始处理
int x = arr[i]; //用x存放现在处理的数据,以便后面进行数据的插入操作。
int j = i -1 ;
for (;j >= 0 && arr[j] > x;j --) //将待处理的元素与这一元素前面的元素进行比较,这里循环中的x不可以写成arr[i],因为此时arr[i]上的元素可能已经变化,不是原来的值
arr[j + 1] = arr[j]; //进行数据的移动操作
arr[j + 1] = x; //将处理的这个元素插入到合适的位置
}
}
}
//测试
public static void main(String[] args) {
int[] a = {
3,1,5,7,2,8,4,9,6};
sort(a);
print(a);
}
}
运行结果:
(2)折半插入排序
查找操作可使用"折半查找"。
(3)希尔排序 / 缩小增量排序 / Shell’s Sort
基本思想:先将整个待排 记录序列 分割成若干子序列 分别进行直接插入排序,待整个序列中的记录"基本有序"时,再对全体记录进行一次直接插入排序。
示例:
特点:子序列的构成不是简单地"逐段分割",而是将相隔某个"增量"地记录组成一个子序列。
三、交换排序
(1)冒泡排序 / 起泡排序 / Bubble Sort
基本思想:
示例:
代码实现:
public class BubbleSort {
//输出数组中的元素
public static void print(int[] arr) {
//这里一定要是static
System.out.print("将数组中的元素从小到大进行排序:");
for (int i = 0;i < arr.length;i++)
System.out.print(arr[i] + " ");
}
public static void sort(int[] arr) {
for (int i = 0;i < arr.length - 1 ;i ++) {
boolean flag = false; //表示本趟冒泡是否发生交换的标志
for (int j = arr.length - 1 ;j > i ;j --) //一趟冒泡过程
if (arr [j - 1] > arr [j]) {
//若为逆序
//swap (arr[j-1],arr[j]) 交换
int temp = arr[j];
arr[j] = arr[j - 1];
arr[j - 1] = temp;
flag = true;
}
if (flag == false) //本趟遍历后没有发生交换,说明表已经有序
break; //或者return;
}
}
//测试
public static void main(String[] args) {
int[] a = {
3,1,5,7,2,8,4,9,6};
sort(a);
print(a);
}
}
运行结果:
(2)快速排序 / QuickSort
是对冒泡排序地改进。
基本思想:通过一趟排序将 待排记录 分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
示例:
代码实现:
public class QuickSort {
//输出数组中的元素
public static void print(int[] arr) {
//这里一定要是static
System.out.print("将数组中的元素从小到大进行排序:");
for (int i = 0;i < arr.length;i++)
System.out.print(arr[i] + " ");
}
public static void sort(int[] arr,int low,int high) {
if (low < high){
//递归跳出的条件
int pivotpos = Partition(arr,low,high); //划分
sort(arr,low,pivotpos - 1);
sort(arr,pivotpos + 1 ,high);
}
}
//划分元素操作,将表arr[low...high]划分为满足上述条件的两个子表
public static int Partition(int[] arr,int low,int high) {
//一趟划分
int pivot = arr[low]; //设当前表中第一个元素为枢轴,对表进行划分
while(low < high) {
//循环跳出条件
while (low < high && arr[high] >= pivot) --high;
arr[low] = arr[high]; //将比枢轴小的元素移动到左端
while (low < high && arr[low] <= pivot) ++low;
arr[high] = arr[low]; //将比枢轴大的元素移动到右端
}//while
arr[low] = pivot; //枢轴元素存放到最终位置
return low; //返回存放枢轴的最终位置
}
//测试
public static void main(String[] args) {
int[] a = {
3,1,5,7,2,8,4,9,6};
sort(a,0,a.length - 1);
print(a);
}
}
运行结果:
四、选择排序
基本思想:每一趟在 n-i+1 (i = 1,2,…,n-1)个记录中选取关键字最小的记录作为有序序列中第 i 个记录。
(1)简单选择排序 / SelectSort
基本操作:首先在未排序数列中找到最小元素,然后将其与数列的首部元素进行交换,然后,在剩余未排序元素中继续找出最小元素,将其与已排序数列的末尾位置元素交换。以此类推,直至所有元素圴排序完毕.
第 i 趟简单选择排序的操作为:通过 n-i 次关键字间的 比较,从 n-i+1 个记录中选出关键字最小的记录,并和第 i (1 ≤ i ≤ n)个记录交换。
假设排序表为L [ 1…n ],第 i 趟排序即从L[ i … n ]中选择关键字最小的元素与L(i)交换,每一趟排序可以确定一个元素的最终位置,这样经过 n - 1 趟排序就可使得整个排序表有序。
代码实现:
public class SelectSort {
//输出数组中的元素
public static void print(int[] arr) {
//这里一定要是static
System.out.print("将数组中的元素从小到大进行排序:");
for (int i = 0;i < arr.length;i++)
System.out.print(arr[i] + " ");
}
public static void sort(int[] arr) {
for (int i = 0; i < arr.length - 1 ;i ++){
//一共进行 n - 1 趟
int min = i; //记录最小元素位置
for (int j = i + 1 ;j < arr.length;j ++) //在arr[i...n-1]中选择最小的元素
if (arr[j] < arr[min])
min = j; //更新最小元素位置
if (min != i) {
//swap (arr[i],arr[min]) 交换
int temp = arr[i];
arr[i] = arr[min];
arr[min] = temp;
}
}
}
//测试
public static void main(String[] args) {
int[] a = {
3,1,5,7,2,8,4,9,6};
sort(a);
print(a);
}
}
运行结果:
(2)堆排序 / Heap Sort
堆排序需要一个记录大小的辅助空间,每个待排序的记录仅占有一个存储空间。
五、归并排序 / MergeSort
"归并"的含义是将两个或两个以上的有序表组合成一个新的有序表。
示例:2路归并排序(两两归并)
代码实现:
public class MergeSort {
//输出数组中的元素
public static void print(int[] arr) {
//这里一定要是static
System.out.print("将数组中的元素从小到大进行排序:");
for (int i = 0;i < arr.length;i++)
System.out.print(arr[i] + " ");
}
public static void sort(int[] arr,int low,int high) {
if (low < high){
//递归跳出的条件
int mid = (low + high) / 2; //从中间划分两个子序列
sort(arr,low,mid); //对左侧子序列进行递归排序
sort(arr,mid+1,high); //对右侧子序列进行递归排序
Merge(arr,low,mid,high); //归并
}
}
//Merge()的功能是将前后相邻的两个有序表归并为一个有序表。
//设两段有序表arr[low...mid]、arr[mid+1...high]存放在同一顺序表中的相邻位置,先将它们复制到辅助数组temp中。
//每次从对应temp数组中的两个段取出一个记录进行关键字比较,将较小者放入arr中,
//当辅助数组temp中有一段的下标超出其对应的表长(即该段的所有元素都已经复制到数组arr中)时,将另一段中的剩余部分直接复制到arr数组中。
public static void Merge(int[] arr,int low,int mid,int high) {
//表arr的两段A[low...mid]和arr[mid+1...high]各自有序,将它们合并成一个有序表
//Java中没有malloc对内存进行直接操作的函数,直接使用new来申请。
int[] temp = new int[arr.length+1]; //创建一个辅助数组temp
int i,j,k;
for (k = low;k <= high; k ++)
temp[k] = arr[k]; ///将arr中所有元素复制到temp中
for (i =low,j = mid + 1,k = i;i <= mid && j <= high ;k ++) {
if (temp[i] <= temp[j]) //比较temp的左右两段中的元素
arr[k] = temp[i++]; //将较小值复制到arr数组中
else
arr[k] = temp[j++];
}//for
//下面的两个while循环只有1个会执行
while (i <= mid)
arr[k++] = temp[i ++]; //若第一个表未检测完,复制
while (j <= high)
arr[k++] = temp[j++]; //若第二个表未检测完,复制
}
//测试
public static void main(String[] args) {
int[] a = {
3,1,5,7,2,8,4,9,6};
sort(a,0,a.length - 1);
print(a);
}
}
运行结果:
六、基数排序
前面所讲述的排序主要是通过关键字间的比较和移动记录两种操作。
通过比较两个关键字的大小,确定对应元素的前后关系,然后通过移动元素以达到有序。
基数排序不需要进行 记录关键字 间的比较。
基数排序是一种借助多关键字排序的思想对单逻辑关键字进行排序的方法。
示例: