直接插入排序,联想记忆——打扑克牌。
- 平均时间复杂度O(n^2)
- 稳定排序
- 更适合于初始记录基本有序的情况
public void insertSort(int[] a) {
for (int i = 1; i < a.length; i++) {
//需要插入排序
if (a[i] < a[i - 1]) {
int tmp = a[i]; //哨兵sentry
a[i] = a[i - 1];
int j = i - 2;
for (; j >= 0 && tmp < a[j]; j--) { //从后往前
a[j + 1] = a[j];
}
a[j + 1] = tmp;
}
}
}
折半插入排序,根据待插入的序列已经排序好的特点,可以利用二分查找的方法提高插入位置的查找效率(比较次数显著降低)。
- 平均时间复杂度O(n^2)
- 稳定排序
- 适合初始记录无序,n较大时
public void bInsertSort(int[] a) {
for (int i = 1; i < a.length; i++) {
int tmp = a[i]; //监视哨
int left = 0, right = i - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (tmp < a[mid]) {
right = mid - 1;
} else {
left = mid + 1;
}
}
//待插入位为right+1, 从right+1开始右移
for (int j = right + 1; j < i; j++) {
a[j + 1] = a[j];
}
a[right + 1] = tmp;
}
}
交换排序
冒泡排序,每排序两两比较相邻的记录,如果发生逆序就交换,从而使小的关键字如气泡一般往左移(或者大的关键字如气泡一般往右移)。
- 平均时间复杂度O(n^2)
- 最好情况是序列为正序只需进行一趟排序,过程只进行n-1次比较且不移动记录
- 最坏情况是序列为逆序。需要进行n-1趟排序
- 稳定排序
- 移动记录次数较多,平均性能比直接插入排序差,当初始序列无需,n较大时不宜使用
public void bubbleSort(int[] a) {
int flag = 1; //每一趟的标记位,如果该趟没有发生排序,说明序列已经正序了
for (int i = 0; i < a.length - 1 && flag == 1; i++) {
flag = 0;
for (int j = 0; j < a.length - 1 - i; j++) {
if (a[j] > a[j + 1]) {
flag = 1;
int tmp = a[j];
a[j] = a[j + 1];
a[j + 1] = tmp;
}
}
}
}
快速排序,由冒泡排序改进,每趟排序以选定的枢轴为分界,分别将小于枢轴的记录交换到枢轴左边,大于枢轴的记录交换到枢轴右边,并返回枢轴下标,最终结合递归完成算法
- 平均时间复杂度O(nlogn)
- 最坏时间复杂度O(n^2) ,递归树退化为线性表
- 不稳定排序
- 适合初始记录无序、n较大的场景,当n较大时,平均情况下快排是所有内部排序方法中最快的一种
public int partition(int[] a, int left, int right) {
//枢轴记录(默认选最左)
int pivot = a[left];
while (left < right) {
while (a[right] >= pivot && left < right) right--;
a[left] = a[right];
while (a[left] <= pivot && left < right) left++;
a[right] = a[left];
}
a[left] = pivot;
return left;
}
public void quickSort(int[] a, int left, int right) {
if (left < right) {
int pivotIndex = partition(a, left, right);
quickSort(a, left, pivotIndex - 1);
quickSort(a, pivotIndex + 1, right);
}
}
public void quickSort(int[] a) {
quickSort(a, 0, a.length - 1);
}
选择排序
基本思想:每一次从待排序的记录中选出最小的记录,按顺序放在已排序的记录序列最后面,直到全部排完为止。
简单选择排序(Simple Selection Sort),或称直接选择排序
- 平均时间复杂度O(n^2),因为无论初始序列排列如何,所需进行记录间的比较次数都是n(n-1)/2,即 n^2
- 就选择排序算法本身而言,它是一种稳定的排序方法,但是简单选择排序由于所采用的“交换记录”策略的问题,造成了这种简单选择排序是不稳定的
- 移动记录次数较少(相反比较次数较多且恒定),当每一个记录占用的空间较多时,此方法比直接插入排序快
public void selectSort(int[] a) {
for (int i = 0; i < a.length; i++) {
int min = i;
//find the index of min
for (int j = i + 1; j < a.length; j++) {
if (a[j] < a[min]) {
min = j;
}
}
//exchange
int tmp = a[i];
a[i] = a[min];
a[min] = tmp;
}
}
堆排序,改进简单选择排序应从减少“比较次数”角度出发,在n个记录中选出最小值,至少需要进行n-1次比较,若能利用前n-1次比较所得的信息,那么接下来从剩余n-1个记录选择最小值就不需要再进行n-2次比较,以后的各趟以此类推。堆排序是一种树形排序,它利用了完全二叉树来实现上述说的比较信息的存储。
- 平均、最差时间复杂度均为O(nlogn)
- 不稳定排序
- 记录数较少时不宜使用,因为建堆比较耗时;记录数较多时较为高效
private void adjustHeap(int[] a, int s, int m) {
//a[s+1, ..., m]已经是大根堆
int heap = a[s];
for (int i = 2 * s + 1; i <= m; i = i * 2 + 1) {
//找出左右孩子中的最大节点
if (i < m && a[i] < a[i + 1]) ++i;
//堆顶最大,无需再调整
if (heap >= a[i]) break;
//堆顶往下筛选
a[s] = a[i];
s = i;
}
a[s] = heap;
}
private void createHeap(int[] a) {
//完全二叉树节点数为N时,所有序号(从0开始)大于 (N/2下界值 - 1)的节点都是叶子,以这些叶子为根的子树均是堆
//自底向上调整
int n = a.length;
for (int i = n / 2 - 1; i >= 0; i--) {
adjustHeap(a, i, n - 1);
}
}
public void heapSort(int[] a) {
createHeap(a);
for (int i = a.length - 1; i > 0; i--) {
//依次将栈顶元素与最后一个元素交换
int tmp = a[i];
a[i] = a[0];
a[0] = tmp;
adjustHeap(a, 0, i - 1);
}
}
归并排序
就是将两个或两个以上的有序表合并成一个有序表的过程。
2-路归并,将两个有序表合并成一个有序表的过程,最简单最常用。
- 平均时间复杂度O(nlogn)
- 稳定排序
//两个相邻有序子序列的归并
private void merge(int[] a, int left, int mid, int right) {
int[] b = new int[a.length]; //临时数组
int i = left, j = mid + 1, k = left;
while (i <= mid && j <= right) {
if (a[i] <= a[j]) b[k++] = a[i++];
else b[k++] = a[j++];
}
while (i <= mid) b[k++] = a[i++]; //将剩余的a[i,...mid]加入到b中
while (j <= right) b[k++] = a[j++]; //同上
for (i = left; i <= right; i++) {
a[i] = b[i];
}
}
private void mergeSort(int[] a, int left, int right) {
if (left < right) {
int mid = left + (right - left) / 2; //序列一分为二
mergeSort(a, left, mid); //对左子序列进行归并排序
mergeSort(a, mid + 1, right); //对右子序列进行归并排序
merge(a, left, mid, right); //左右归并,得出结果
}
}
public void mergeSort(int[] a) {
int n = a.length;
mergeSort(a, 0, n - 1);
}
扩展:排序算法的稳定性指的是什么?
稳定性:稳定排序算法会让原本有相等键值的纪录维持相对次序。也就是如果一个排序算法是稳定的,当有两个相等键值的纪录R和S,且在原本的列表中R出现在S之前,在排序过的列表中R也将会是在S之前。
假设参加排序的元素有如下:
(4, 1) (3, 1) (3, 7)(5, 6)
不同排序算法产生如下结果: ① (3, 1) (3, 7) (4, 1) (5, 6) (相同键值的次序保持不变) ② (3, 7) (3, 1) (4, 1) (5, 6) (相同键值的次序被改变)
我们就说算法①是稳定排序,算法②是不稳定排序。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)