排序是最基础的算法,从排序的对象来说主要分为内部排序和外部排序。内部排序主要是针对内存中的数据进行排序,外部排序针对外存如硬盘、光盘中的数据进行排序。内部排序按工作方式主要分为:插入排序(直接插入排序、希尔排序)、选择排序(简单选择排序、堆排序)、交换排序(冒泡排序、快速排序)、归并排序、基数排序。
1.直接插入排序
其基本原理就是从不断从待排序集合中选取元素,逐个插入到有序的集合中,直到取出待排序集合中全部元素。就像我们日常生活中玩扑克牌一样,一边从桌子上抽牌,一边将抽取的牌插入到手中合适的位置。其实现代码如下:
/**
* insertSort
* @author liebert
* @param int arr[] 待排序数组
* @param int len 数组长度
*/
void insertSort (int arr[], int len)
{
int i, j, key;
for (i = 1; i < len; i++) {
key = arr[i];
j = i - 1;
while (j >= 0 && key <= arr[j]) {
arr[j+1] = arr[j];
j--;
}
arr[j+1] = key;
}
}
javascript版本
function insertSort(arr){
let len = arr.length,
i, j, temp;
for (i = 0; i < len - 1; i++) {
j = i+1;
temp = arr[j];
while (j > 0 && temp > arr[j-1]){
arr[j] = arr[j-1];
j--;
}
arr[j] = temp;
}
}
2.简单选择排序
其基本工作原理是从左只右开始对集合进行扫描,集合左部分为有序集合,集合右不分为待排序集合,每次从待排序集合中选出一个最大的放入左侧有序集合中个,直到右侧待排序集合元素为空。其实现代码如下:
/**
* selectSort
* @author liebert
* @param int arr[] 待排序数组
* @param int len 数组长度
*/
void selectSort (int arr[], int len)
{
int i, j, k, temp;
for (i = 0; i < len-1; i++) {
k = i;
for (j = i + 1; j < len; j++) {
if (arr[j] < arr[k]){
k = j;
}
}
if (i != k){
temp = arr[i];
arr[i] = arr[k];
arr[k] = temp;
}
}
}
3.冒泡排序
其基本工作原理是对待排序集合进行遍历,从左至右开始,逐个元素进行扫描,如果左侧元素大于右侧(按升序)则对两个元素进行交换,直到待排序集合末尾,此时通过交换获得了最大的元素。调整右侧有序集合边界向左前进一个。其实现代码如下:
/**
* bubbleSort
* @author liebert
* @param int arr[] 待排序数组
* @param int len 数组长度
*/
void bubbleSort (int arr[], int len)
{
int i, j, temp, flag;
for (i = 0; i < len-1; i++) {
flag = 0;
for (j = 0; j < len-i-1; j++) {
if(arr[j] > arr[j+1]){
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
flag = 1;
}
}
if (0 == flag) {
return;
}
}
}
4.快速排序
其基本原理是采用分治思想,将待排序集合通过中间元素划分为两个子集合,使得中间元素大于其左侧集合中的每一个元素,并且小于其右侧集合中的每一个元素。重复上述过程指导子集合中的元素个数为1。其实现代码如下:
/**
* quickSort
* @author liebert
* @param int arr[] 待排序数组
* @param int l 左边界
* @param int r 右边界
*/
void quickSort (int arr[], int l, int r)
{
int mid;
if(l < r){
mid = partition(arr, l, r);
quickSort(arr, l, mid-1);
quickSort(arr, mid+1, r);
}
}
/**
* partition
* @author liebert
* @param int arr[] 待分割数组
* @param int l 左边界
* @param int r 右边界
*/
int partition (int arr[], int l, int r)
{
int i, j, mid, temp;
for (i = l-1, j = l; j < r; j++){
if (arr[r] > arr[j]) {
i++;
if (i != j){
temp = arr[j];
arr[j] = arr[i];
arr[i] = temp;
}
}
}
i++;
temp = arr[r];
arr[r] = arr[i];
arr[i] = temp;
return i;
}
javascript版本:
function quickSort(arr, left, right) {
var i = left - 1,
j = left,
temp;
if(left < right) {
for (; j < right; j++) {
if (arr[right] > arr[j]) {
i++;
if (i != j) {
temp = arr[j];
arr[j] = arr[i];
arr[i] = temp;
}
}
}
i++;
temp = arr[right];
arr[right] = arr[i];
arr[i] = temp;
quickSort(arr, left, i-1);
quickSort(arr, i+1, right);
}
return;
}
5.归并排序
归并排序也是采用分治思想的一种排序方法。先使每个子序列有序,再使子序列段间有序,然后将已有序的子序列合并,得到完全有序的序列。其处理流程:首先对待排序集合进行划分,划分为两个较小非集合;比较a[i]和b[j]的大小,若a[i]≤b[j],则将第一个有序表中的元素a[i]复制到r[k]中,并令i和k分别加上1;否则将第二个有序表中的元素b[j]复制到r[k]中,并令j和k分别加上1,如此循环下去,直到其中一个有序表取完;然后再将另一个有序表中剩余的元素复制到r中从下标k到下标t的单元。归并排序的算法我们通常用递归实现,先把待排序区间[s,t]以中点二分,接着把左边子区间排序,再把右边子区间排序,最后把左区间和右区间用一次归并操作合并成有序的区间[s,t]。其实现代码如下:
/**
* mergeSort
* @author liebert
* @param int arr[] 待分割数组
* @param int l 左边界
* @param int r 右边界
*/
void mergeSort(int arr[], int result[], int l, int r)
{
int i, j, mid, m;
if (l < r) {
mid = (l + r) / 2; // 向下取整
mergeSort(arr, result, l, mid);
mergeSort(arr, result , mid+1, r);
// 合并arr[l, mid]和arr[mid+1, r]
i = l;
j = mid+1;
m = l;
while (i<=mid && j<=r) {
if (arr[i] < arr[j]) {
result[m++] = arr[i++];
} else {
result[m++] = arr[j++];
}
}
while (i<=mid) {
result[m++] = arr[i++];
}
while (j<=r) {
result[m++] = arr[j++];
}
for(i=l; i<=r; i++) {
arr[i] = result[i];
}
}
}
6.希尔排序
希尔排序是对直接插入排序的一种改进,首先采用分治的思想缩小排序集合的规模,也就说将待排序集合进行分组,然后对分组后的集合采用插入法进行排序,通过不断缩小分组数量再排序直到分组为1,就完成了全部排序过程。
/**
* shellSort
* @author liebert
* @param int arr[] 待分割数组
* @param int len 数组长度
*/
void shellSort(int arr[], int len)
{
int i, j, dk, temp;
// 分组
for (dk = len / 2; dk > 0; dk /=2) {
// 插入排序
for (i = dk; i < len; i++) {
temp = arr[i];
j = i - dk;
while (j >= 0 && temp < arr[j]) {
arr[j+dk] = arr[j];
j -= dk;
}
arr[j+dk] = temp;
}
}
}
javascript版本
function shellSort(arr){
let len = arr.length,
dk = Math.floor(len / 2),
temp, i, j;
for (; dk > 0; dk = Math.floor(dk / 2)) {
for (i = dk; i < len; i += dk) {
j = i;
temp = arr[j];
while (j > 0 && temp > arr[j-dk]) {
arr[j] = arr[j-dk];
j -= dk;
}
arr[j] = temp;
}
}
}
7.堆排序
堆是一个数组,在逻辑上是一个近似的完全二叉树,树上的每一个节点对应数组中的元素,数组的索引被用作树中节点的序号。对于一个序号为i的节点来说,左叶子节点为2i,右叶子节点为奇数2i+1,父节点为i/2。
堆排序的基本思路是:
① 先将初始数组R[1..n]建成一个大根堆,此堆为初始的无序区;
② 再将关键字最大的记录R[1](即堆顶)和无序区的最后一个记录R[n]交换,由此得到新的无序区R[1..n-1]和有序区R[n],且满足R[1..n-1].keys≤R[n].key;
③由于交换后新的根R[1]可能违反堆性质,故应将当前无序区R[1..n-1]调整为堆。然后再次将R[1..n-1]中关键字最大的记录R[1]和该区间的最后一个记录R[n-1]交换,由此得到新的无序区R[1..n-2]和有序区R[n-1..n],且仍满足关系R[1..n-2].keys≤R[n-1..n].keys,同样要将R[1..n-2]调整为堆。
void swap(int *a, int *b)
{
int temp;
temp = *a;
*a = *b;
*b = temp;
}
/**
* heapAdjust
* @description 调整堆
* @author liebert
* @param int arr[] 待分割数组
* @param int len 数组长度
* @param int index 当前节点索引
*/
void heapAdjust(int arr[], int len, int index)
{
int left = index*2+1; // 左孩子节点索引
int right = index*2+2; // 右孩子节点索引
int large = index; // 最大节点索引
if (left <= len-1 && arr[large] < arr[left]) {
large = left;
}
if (right <= len-1 && arr[large] < arr[right]) {
large = right;
}
if (large != index){
swap(&arr[index], &arr[large]);
heapAdjust(arr, len, large);
}
}
/**
* heapBuild
* @description 创建堆
* @author liebert
* @param int arr[] 待分割数组
* @param int len 数组长度
*/
void heapBuild(int arr[], int len)
{
int i;
for (i = len / 2 - 1; i >= 0 ; i--) {
heapAdjust(arr, len, i);
}
}
/**
* heapSort
* @author liebert
* @param int arr[] 待分割数组
* @param int len 数组长度
*/
void heapSort(int arr[], int len)
{
int i ;
// 建堆
heapBuild(arr, len);
// 交换
for (i = len; i > 1; i--) {
swap(&arr[0], &arr[i]);
// 调整堆
heapAdjust(arr, i-1, 0);
}
}
分析
(1)对于时间复杂度为O(N)的排序算法
时间复杂度为O(N)的算法主要有基数排序、计数排序,但是这两种算法都需要提前知道数组中元素的范围,以此来建立桶的数量,因此并不合适来解决这个问题。
(2)对于时间复杂度为O(N2)的排序算法
时间复杂度O(N2)常用的主要有冒泡、选择、插入,联系到题目所说的基本有序,插入排序是首选,每个元素移动的距离不超过K,因此插入排序中每个元素向前移动的距离也不会超过K,故此时插入排序的时间复杂度为O(N*K),所以插入排序可以列入考虑。
(3)对于时间复杂度为O(N*logN)的排序算法
时间复杂度O(N*logN)常用的主要有快速排序、归并排序、堆排序,因为这两种排序方法跟数组元素的初始顺序无关,因此这两种方法也是比较好的一种。而堆排序在最好、最坏、平均情况下时间复杂度都为O(N*logN)。