一、排序
排序是对某一组数据进行递增或者递减的排列。
排序的稳定性:如果待排序的数据中,具有多个相同的关键字,经过排序后,上述关键字的相对顺序不变则称该排序为稳定排序,否则为不稳定排序。
二、插入排序
每次将一个待排序的元素,按照其关键字大小插入到已经排好序的数据的适当位置,直至所有元素都有序。
1. 直接插入排序
所有元素分为有序区和无序区,将无序区的第一个元素插入到有序区中适当的位置,直到无序区中没有任何元素。时间复杂度为O(n2)
按升序排列:
void insertSort(T R[], int n){
int i, j;
T tmp;
for(int i = 1; i < n; i++){ //第一个元素已经在有序区中
tmp = R[i];
j = i - 1;
while(j >= 0 && tmp < R[j]){ //从右往左在有序区中查找R[i]插入的位置
R[j+1] = R[j]; //将关键字大于R[i]的元素后移
j--;
}
R[j+1] = tmp; //在j+1处插入R[i]
}
}
j = i-1;的原因是取有序区的最后一个元素,然后从后往前逐一比较,找到适合tmp插入的位置,如果j = 0;从0开始查找适合的位置要使用一个新的循环对有序区进行查找。
while(j >= 0 && tmp < R[j])当判断条件改为tmp<=R[j]的话,排序就变为不稳定了,会导致后面相同的元素插到第一个相同元素之前。
2.折半插入排序
在直接插入排序的基础上,对有序区采用折半查找的方法找到插入位置。时间复杂度也为O(n2),但是理论上性能要比直接插入排序要好。
void insertSort1(T R[], int n){
int i, j, low, mid, high;
T tmp;
for(int i = 1; i< n; i++){
tmp = R[i]; //将R[i]保存到tmp中
low = 0;
high = i - 1;
while(low <= high){ //在R[low .. high]中查找插入的位置
mid = (low + high)/2;
if(tmp < R[mid])
high = mid -1; //插入点在左半区
else
low = mid + 1; //插入点在右半区
}
for(j = i -1; j >= high +1; j--){ //元素后移
R[j+1] = R[j];
}
R[high + 1] = tmp; 插入
}
}
3.希尔排序
希尔排序其实是一种分组插入方法,基本思想:选取一个小于n的增量d1,把相隔d1的元素分组并进行直接插入排序,然后再选区一个增量d2(<d1),重复分组和直接插入排序的过程,直到d=1时,排序完成。时间复杂度为O(n1.3)
void shellSort(T R[], int n){
int i, j, gap;
T tmp;
gap = n / 2; //对增量设置初始值
while(gap > 0){
for(i = gap; i < n; i++){ //对相邻gap的元素采用直接插入排序
tmp = R[i];
j = i - gap;
while(j >= 0 && tmp < R[j]){
R[j + gap] = R[j];
j = j - gap;
}
R[j + gap] = tmp;
}
gap = gap / 2; //减少增量
}
}
三、交换排序
交换排序的基本思想是:两两比较待排序元素中的关键字,发现两个元素的次序相反时即进行交换,直到没有反序为止。
1. 冒泡排序
基本思想:通过无序区中相邻元素之间关键字的比较和位置的交换,使得关键字较小的元素如同气泡一样逐渐漂浮直至水面,也就是说每一趟循环都在无序区中找到最小的元素,并将其移动到有序区的末尾。冒泡排序每趟产生的有序区一定是全局有序区。时间复杂度为O(n2)。
算法如下:
void bubbleSort(T R, int n){
int i, j;
bool exchange;
T tmp;
for(i = 0; i < n; i++){
exchange = false;
for(j = n - 1; j > i; j++){ //从后往前比较,找出最小的元素
if(R[j] < R[ j - 1 ] ){ //交换R[j]和R[j-1],将小的元素往前移,直到有序区的末尾停止
tmp = R[j];
R[j] = R[j-1];
R[j - 1] = tmp;
exchange = true;
}
}
if(!exchange) //如果没有发生交换,就结束该算法
return;
}
}
2. 快速排序
基本思想:在待排序区中任选一个元素作为基准,将元素放在适当的位置后,将小于基准的元素放在基准前,将大于基准的元素放在基准后,这称为一趟快速排序,随后对划分出来的两部分重复上述操作,直到每一部分的长度为1或者0。由于是一个递归+二元划分的思想,因此时间复杂度为O(nlog2n);
void quickSort(T R[], int s, int t){
int i = s, j = t;
T tmp;
if(s < t){ //至少存在两个元素
tmp = R[s]; //用区间第一个元素作为基准
while(i != j){ //从两边向中间扫描,直到s = t
while(j > i && R[j] >= tmp) //从右向左扫描,找到第一个小于基准的元素
j--;
R[i] = R[j]; //交换
while(i < j && R[i] <= tmp) //从左向右扫描,找到第一个大于基准的元素
i++;
R[j] = R[i];
}
R[i] = tmp; //放置保存中间值
quickSort(R, s, i - 1); //对左区间递归排序
quickSort(R, i+1, t); //对右区间递归排序
}
}
四、选择排序
基本思想:每一趟从待排序的元素中选出最小(最大)的元素,顺序放置到已排序区的最后,直到全部元素排序完毕。该方法适合在海量元素中选取前几个元素。
1.直接选择排序
基本思想:从无序区中选择最小的一个元素,将其与无序区的第一个元素交换(若第一个元素为无序区最小,则不用交换),相当于将无序区中最小的元素添加到有序区的后面。时间复杂度为O(n2)。
void selectSort(T R[], int n){
int i, j, k;
T tmp;
for(i = 0; i < n; i++){ //做第i趟排序
k = i; //记录当前无序区的第一个元素i
for(j = i + 1; j < n; j++){ //找到无序区最小的元素
if(R[j] < R[k]){
k = j; //记录无序区最小元素的位置k
}
}
if(k != i){ //如果位置k与位置i不相同,则交换R[i]与R[k]
tmp = R[i];
R[i] = R[k];
R[k] = tmp;
}
}
}
2.堆排序
堆排序是一种树形选择排序方法,特点是将数组看成是一颗完全二叉树的顺序存储结构,利用完全二叉树中双亲结点和孩子节点之间的内在关系,在当前无序区中选择关键字最大(最小)的元素。
堆的定义:n个关键字序列K1,K2…Kn称为堆,并且仅当该序列满足如下性质:
1. Ki<=K2i且Ki<=K2i+1 (完全二叉树)
2. 或者Ki>=K2i且Ki>=K2i+1 (大根堆)
堆排序的排序过程与直接选择排序类似,堆排序通过堆的形式选择最小或者最大的元素,将每次挑选出的元素归为,这时树就会被打乱,于是就需要对无序区中的元素重新调整为堆,重复上述步骤直至堆中剩下最后一个元素。
void sift(T R[], int low, int high){ //调整堆的算法
int i = low, j = 2 * i;
T tmp = R[i];
while(j <= high){
if(j < high && R[j] < R[j+1]) //若右孩子较大,则将j指向右孩子
j++;
if(tmp < R[j]){
R[i] = R[j]; //将R[j]调整到双亲结点的位置
i = j; //修改i,j的值,以便继续筛选
j = 2 *i;
}else
break; //筛选结束
}
R[i] = tmp; //被筛选节点的值放入最终位置。
}
通过上述的调整算法得到的结果是:数组中第一个元素为当前无序区中的最大值。排序的核心是通过移动该最大值,再重新调用该算法得到另一个最大值。算法如下:
void heapSort(T R[], int n){
int i;
T tmp;
for(i = n/2; i >= 1; i--){ //循环调整建立初始堆
sift(R, i, n);
}
for(i = n; i >= 2; i--){ //进行n-1 趟排序,每一趟堆排序的元素个数减1,
tmp = R[1]; //将最后一个元素和当前区间内R[1](最大值)交换
R[1] = R[i];
R[i] = tmp;
sift(R, 1, i-1); //调整堆
}
}
五、归并排序
归并排序是多次将两个或者两个以上的有序表合并成一个新的有序表。最简单的归并是直接将两个有序表直接合并成一个有序的表,也就是二路归并。二路归并排序的基本思想:将数组看成是n个长度为1的有序列表,然后进行两两归并,得到n/2个长度为2(最后一个长度可能为1)的有序序列,再进行两两归并,得到n/4个长度为4的有序列表,随后重复上述步骤,直到得到一个长度为n的有序列表。时间复杂度为O(nlog2n),空间复杂度为O(n)。
六、Java API中的排序
1. Arrays类中的静态排序API
Arrays.sort(int[] a) 排序用的是快速排序,时间复杂度为O(nlog2n)
Arrays.sort(T[], Comparator<? super T> c)使用的是归并排序
2. Collections静态排序API,Collections的排序都是稳定的
Collections.sort(List list)和Collections.sort(List list, Comparator<? super T> c);都是稳定排序,主要是对list排序
3.ArrayList的排序API
list.sort(Comparator<? super T> c),对List排序