排序总览
排序大的分类可以分为两种:内排序和外排序。在排序过程中,全部记录存放在内存,则称为内排序,如果排序过程中需要使用外存,则称为外排序。下面讲的排序都是属于内排序。内排序有可以分为以下几类:
(1)、插入排序:直接插入排序、希尔排序
(2)、选择排序:选择排序、堆排序
(3)、交换排序:冒泡排序、快速排序
排序相关定义
- 稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,则称这种排序算法是稳定的;否则称为不稳定的。
- 时间复杂度:对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。
- 空间复杂度:算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数。
排序复杂度
排序算法
插入排序
基本思想
插入排序是一种非常简单的排序方法,它的基本操作是将一个记录插入到已排好序的有序表中,从而得到一个新的、记录数加1的有序表。
算法演示
代码实现
def insertSort(arr):
n = len(arr)
for i in range(1, n):
if arr[i] < arr[i-1]:
cur = arr[i]
j = i - 1
while j >= 0 and arr[j] > cur:
arr[j+1] = arr[j]
j -= 1
arr[j+1] = cur
希尔排序
基本思想
先将整个带排记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录基本有序时,再对全体记录进行一次直接插入排序
算法演示
代码实现
def shellSort(arr):
n = len(arr)
gap = 1
while gap < n/3:
gap = gap*3 + 1
while gap >= 1:
for i in range(gap, n):
if arr[i] < arr[i-gap]:
j = i
cur = arr[i]
while j >= gap and arr[j-gap] > cur:
arr[j] = arr[j-gap]
j -= gap
arr[j] = cur
gap = gap//3
冒泡排序
基本思想
从头依次比较相邻两条记录的关键字,如果是逆序的,则交换,这样,一趟比较的结果就是,最大的记录被安置在最后。然后以此类推,就会得到一个有序的列表
代码实现
def bubbleSort(arr):
n = len(arr)
for i in range(0, n-1):
for j in range(0, n-i-1):
if arr[j] > arr[j+1]:
arr[j],arr[j+1] = arr[j+1],arr[j]
快速排序
基本思想
通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序
算法演示
代码实现
def quickSort(arr):
qSort(arr, 0, len(arr)-1)
def qSort(arr, low, high):
if low < high:
pivotLoc = partition(arr, low, high)
qSort(arr, low, pivotLoc-1)
qSort(arr, pivotLoc+1, high)
def partition(arr, low, high):
pivotIndex = low
pivot = arr[pivotIndex]
while low < high:
while low < high and arr[high] >= pivot:
high -= 1
arr[low] = arr[high]
while low < high and arr[low] <= pivot:
low += 1
arr[high] = arr[low]
arr[low] = pivot
return low
选择排序
基本思想
先在未排序序列中找到最小记录,存放到排序序列的起始位置,然后,再从剩余未排序记录中继续寻找最小元素,然后放到已排序序列的末尾;以此类推,直到所有记录均排序完毕
代码实现
def selectSort(arr):
n = len(arr)
for i in range(0, n-1):
minIndex = i
for j in range(i+1, n):
if arr[j] < arr[minIndex]:
minIndex = j
if minIndex != i:
arr[minIndex], arr[i] = arr[i],arr[minIndex]
堆排序
完全二叉树的性质:
如果n个节点的完全二叉树的节点按照层次并按从左到右的顺序从0开始编号,对于任意一个结点都有:
1). 序号为0的节点是根,对于i>0,其父节点的编号为(i-1)/2
2). 若2*i+1<n,其左子节点的序号为2*i+1,否则没有左子节点
3). 若2*i+2<n,其右子节点的序号为2*i+2,否则没有右子节点
堆定义:
n个元素的序列{k0, k1, k2, k3, ..., kn-1}, 当且仅当满足如下关系时,称之为堆
ki <= k2i+1 and ki <= k2i+2 (小顶堆)
或
ki >= k2i+1 and ki >= k2i+2 (大顶堆)
堆的含义表明,完全二叉树中所有非终端结点的值均不大于(或不小于)其左右孩子结点的值
堆排序:
若在输出堆顶的最小值之后,使得剩余n-1个元素的序列重又建成一个堆,则得到n个元素中的次小值。如此反复执行,便能得到一个有序序列,这个过程称之为堆排序
算法演示:
代码实现:
def heapAdjust(arr, start, end):
i = start*2+1
while i <= end:
if i < end and arr[i] < arr[i+1]:
i += 1
if arr[start] > arr[i]:
break
arr[start],arr[i] = arr[i],arr[start]
start = i
i = i*2+1
def heapSort(arr):
n = len(arr) - 1
for i in range(n/2, -1, -1):
heapAdjust(arr, i, n)
for i in range(n, 0, -1):
arr[0],arr[i] = arr[i],arr[0]
heapAdjust(arr, 0, i-1)
其他
php 的 sort
PHP_FUNCTION(sort)
{
zval *array;
zend_long sort_type = PHP_SORT_REGULAR;
compare_func_t cmp;
ZEND_PARSE_PARAMETERS_START(1, 2)
Z_PARAM_ARRAY_EX(array, 0, 1)
Z_PARAM_OPTIONAL
Z_PARAM_LONG(sort_type)
ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE);
cmp = php_get_data_compare_func(sort_type, 0);
if (zend_hash_sort(Z_ARRVAL_P(array), cmp, 1) == FAILURE) {
RETURN_FALSE;
}
RETURN_TRUE;
}
ZEND_API int ZEND_FASTCALL zend_hash_sort_ex(HashTable *ht, sort_func_t sort, compare_func_t compar, zend_bool renumber)
{
Bucket *p;
uint32_t i, j;
IS_CONSISTENT(ht);
HT_ASSERT_RC1(ht);
if (!(ht->nNumOfElements>1) && !(renumber && ht->nNumOfElements>0)) { /* Doesn't require sorting */
return SUCCESS;
}
if (HT_IS_WITHOUT_HOLES(ht)) {
i = ht->nNumUsed;
} else {
for (j = 0, i = 0; j < ht->nNumUsed; j++) {
p = ht->arData + j;
if (UNEXPECTED(Z_TYPE(p->val) == IS_UNDEF)) continue;
if (i != j) {
ht->arData[i] = *p;
}
i++;
}
}
sort((void *)ht->arData, i, sizeof(Bucket), compar,
(swap_func_t)(renumber? zend_hash_bucket_renum_swap :
((HT_FLAGS(ht) & HASH_FLAG_PACKED) ? zend_hash_bucket_packed_swap : zend_hash_bucket_swap)));
//此处省略100行
}
ZEND_API void zend_sort(void *base, size_t nmemb, size_t siz, compare_func_t cmp,swap_func_t swp)
{
while (1) {
if (nmemb <= 16) {
zend_insert_sort(base, nmemb, siz, cmp, swp);
return;
} else {
char *i, *j;
char *start = (char *)base;
char *end = start + (nmemb * siz);
size_t offset = (nmemb >> Z_L(1));
char *pivot = start + (offset * siz);
if ((nmemb >> Z_L(10))) {
size_t delta = (offset >> Z_L(1)) * siz;
zend_sort_5(start, start + delta, pivot, pivot + delta, end - siz, cmp, swp);
} else {
zend_sort_3(start, pivot, end - siz, cmp, swp);
}
swp(start + siz, pivot);
pivot = start + siz;
i = pivot + siz;
j = end - siz;
while (1) {
while (cmp(pivot, i) > 0) {
i += siz;
if (UNEXPECTED(i == j)) {
goto done;
}
}
j -= siz;
if (UNEXPECTED(j == i)) {
goto done;
}
while (cmp(j, pivot) > 0) {
j -= siz;
if (UNEXPECTED(j == i)) {
goto done;
}
}
swp(i, j);
i += siz;
if (UNEXPECTED(i == j)) {
goto done;
}
}
done:
swp(pivot, i - siz);
if ((i - siz) - start < end - i) {
zend_sort(start, (i - start)/siz - 1, siz, cmp, swp);
base = i;
nmemb = (end - i)/siz;
} else {
zend_sort(i, (end - i)/siz, siz, cmp, swp);
nmemb = (i - start)/siz - 1;
}
}
}
}
问题:
已知一个几乎有序的数组,几乎有序是指,如果把数组排好顺序的话,每个元素移动的距离可以不超过k,并且k相对于数组来说比较小。请选择一个合适的排序算法对数组进行排序。