《数论相关》那篇里最后提到的那道题——回文字符串,应该很多同学都会做吧,不过在这还是班门弄斧说一下思路:
假设有n个字符的字符串中有m1个字符n1,m2个字符n2,...mk个字符nk,则,m1 + m2 + ... + mk = n 且 m1,m2,...,mk中只能有1个奇数。这n个字符的字符串可以组合成的回文字符串个数,即转换成对m1/2个n1,m2/2个n2,...,mk/2个nk的全排列个数(n/2)!/(m1/2)!(m2/2)!...(mk/2)! (上篇博文提示1),根据提示2,将(n/2)!/(m1/2)!(m2/2)!...(mk/2)!的每一个阶乘均表示成不大于n/2的所有素数的阶乘积的形式则阶乘的除法即转换成减法。
本篇总结一下学过的基础排序算法,只给出伪代码(自己亲手写出的代码才是自己的):
1.插入排序
直接插入排序
定义:在长度为n的数列中,将第i个元素与第0~(i-1)个元素(已排序)从右到左依次比较,插入到合适的位置,使第0~i个元素也有序(0<= i < n)。算法导论中的 一个非常形象的比喻,从牌堆中一张一张抓牌,将抓到的牌,与手中已排好的牌从右到左依次比较,插到正确的位置。算法复杂度:O(n^2)伪代码:insert_sort(A)for j = 2 to A.lengthkey = A[j]i = j - 1while i > 0 && A[i] > keyA[i+1] = A[i];i = i - 1A[i + 1] = key
希尔排序
定义:先将整个数列分割成若干个子数列分别进行直接插入排序,待整个数列中的记录"基本有序"时,再对全体记录进行一次直接插入排序,适用于部分有序的数列。子数列的分割原则:
1.将相隔某个增量的元素组成一个子序列2.每次设定的增量递减直到最后为1,并且增量序列的值必须互质3.增量序列的取得方法尚未求出
部分有序:
1.每个元素距离它的最终位置不远2.一个有序大数组接一个小数组3.数组中只有几个元素的位置不正确
2.选择排序
简单选择排序
定义:在长度为n的数列中,从(n - i + 1)个数中选出最大(最小)的数与第i个数交换(1<= i <= n)算法复杂度:O(n^2)伪代码:
select_sort(A)for i = 1 to A.lengthl = i;for j = i to (n - i + 1)if A[j] > A[l]l = jexchange i and l
堆排序
改进的选择排序算法,利用一种叫“堆”的数据结构进行信息管理。堆不仅用在堆排序中,也可以构造一种有效的优先的队列(应用很多,如共享计算机系统的作业调度)。堆(二叉堆)
定义:是一个数组,可以被看成是一个近似的完全二叉树,树上每一个节点依层次分别对应数组的一个元素。最大(小)堆 / 大(小)顶堆:除根节点外所有节点的父节点都不比自身小(大)堆的操作(最大堆):1.堆维护:当数组中某个位置index的元素不满足其所在的堆的性质时(该元素的值小于其子元素),需要将该元素与子节点中最大的交换位置,再以交换后的位置 index1递归次过程,直到不需要与子节点交换位置为止伪代码:max_heapify(A, i)
l_child = 2ir_child = 2i + 1largest = i;if l_child <= A.length && A[l_child] > A[i]largest = l_childif r_child <= A.length && A[r_child] > A[largest]largest = r_childif (largest != i)exchange A[largest] with A[i]max_heapify(A, largest]
2.建堆:可以通过1.堆维护,将长度为n的数组A转换成最大堆,因为子数组(A[n/2 + 1]~A[n])都是树的叶子节点,可以看成只有1个元素的大顶堆,则可以依次对 元素 A[n/2]~A[1]依次调用堆维护,则可以生成一个最大堆伪代码:build_max_heap(A)for i = A.length / 2 to 1max_heapify (A, i)堆排序
定义:利用以上两个堆的操作即可完成排序算法。在长度为n的数列中,先建好堆,循环以下两步:1.取出根元素,将最后一个叶子结点放入根节点位置2.对根节点做堆维护算法复杂度:O(nlgn)伪代码:heap_sort(A)
build_max_heap(A)for i = A.length to 2exchange A[i] with A[1]copy A[A.length] to A[A.length - 1]max_heapify(A[length - 1], 1)
优先队列
同样是利用堆的数据结构,根据关键字的优先级存储关键字,应支持以下操作:1. insert(S, key):关键字插入2. maximum(S):返回优先级最大关键字3.extract_maximum(S):取得优先级最大关键字并将其从队列中去除
3.归并排序
定义:将两个或两个以上的有序表组合成一个新的有序表算法复杂度:O(nlgn)伪代码:递归实现的两路归并排序merge_sort(A, s, e)
if s < emid = s + (e - s)>> 1;merge_sort(A, s, mid)merge_sort(A,mid + 1, e)merge(A, s , mid, e)
merge(A, s, mid, e)
tmp = int[e-s+1]for i = s, j = mid + 1 to mid, eif A[i] > A[j] tmp[k++] = A[j++]else tmp[k++] = A[i++]while i <= mid tmp[k++] = A[i++]while j <= e tmp[k++] = A[j++]A[s,e] = tmp[0,e-s]
4.快速排序
定义:从数列A[p,q]中任意选取一个值作为pivot(支点),将数列划分为两个子数列A[p,r],A[r+1,q],使得A[p,r]的所有元素均小于pivot,而A[r+1,q]的所有元素均大于 pivot,然后对分成的两个子数列递归调用此过程算法复杂度:O(nlgn)伪代码:quick_sort(A,p,q)
if p < qr = partition(A,p, q)quick_sort(A,p,r-1)quick_sort(A,r+1,q)
1.常见的两头扫描实现partitionpartition(A,low,high)
pivot = A[low]while low < highwhile low < high && A[high] >= pivot high--A[low] = A[high]while low < high && A[low] <= pivot low++A[high ] = A[low ]A[low] = pivotreturn low
2.单头扫描实现partition(循环里的判断是:如果碰到一个小于pivot的元素,则把该元素与第一个大于pivot的元素元素进行交换)partition(A,low,high)
pivot = A[high]i = low -1for j = low to high - 1if A[j] <= pivoti = i + 1exchange A[i] with A[j]exchange A[i+1] with A[high]return i+1