说实话,我从来没有能一次写对一个快速排序,总是有各种各样的错误。快排麻烦就麻烦在,没办法去调试它,因为它是 生成递归的,只能去静态调试,或者是不断的打印数组的状态以推测错误的可能性。 然而快排的基本思想却是极其简单的: 接收一个数组,挑一个数,然后把比它小的那一摊数放在它的左边,把比它大的那一摊数放在它的右边,然后再对这个数左右两摊数递归的执行快排过程,直到子数组只剩一个数为止。
void quicksort(int array[], int left, int right) { //Do nothing if left <= right //p <- Get a number from array //Put elements <= p to the left side //Put elements >= p to the right side //Put p in the middle slot which index is pivot //Recursive quicksort the left parts and right parts }然后慢慢的把这些伪代码转化成C code:
void quicksort(int array[], int left, int right) { if(left<right) { //p <- Get a number from array //Put elements <= p to the left side //Put elements >= p to the right side //Put p in the middle slot which index is pivot //Recursive quicksort the left parts and right parts } }
void quicksort(int array[], int left, int right) { if(left<right) { int p=array[left]; //Put elements <= p to the left side //Put elements >= p to the right side //Put p in the middle slot which index is pivot //Recursive quicksort the left parts and right parts } }
void quicksort(int array[], int left, int right) { //Do nothing if left <= right if(left<right) { //p <- Get a number from array int p=array[left]; //Put elements < p to the left side //Put elements >= p to the right side int i=left,j; for(j=left+1;j<=right;j++) { if(array[j]<p) { i++; swap(array,i,j); } } //Put p in the middle slot which index is pivot swap(array,i,left); //Recursive quicksort the left parts and right parts } }
void partition(int array[],int left, int right) { //p <- Get a number from array int p=array[left]; //Put elements < p to the left side //Put elements >= p to the right side int i=left,j; for(j=left+1;j<=right;j++) { if(array[j]<p) { i++; swap(array,i,j); } } //Put p in the middle slot which index is pivot swap(array,i,left); return i; }
void quicksort(int array[], int left, int right) { //Do nothing if left <= right if(left<right) { int pivot=partition(array,left,right); //Recursive quicksort the left parts and right parts quicksort(array,left,pivot-1); quicksort(array,pivot+1,right); } }
void q_sort(int array[], int size) { quicksort(array, 0, size-1); }当然上面的步骤省去了很多的测试及调试过程,之前我也提到过了,我做不到一下子写一个正确的快排,但一步一步来的话,还是可以写出来的。 接下来回到标题:quicksort能够写多短? 拿之前的C程序来说,把它弄短的途径无非是展开函数,去除大括号等山寨方法。
void quicksort(int array[], int left, int right) { if(left<right) { int p=array[left]; int pivot=left,j; for(j=left+1;j<=right;j++) { if(array[j]<p) { pivot++; swap(array,pivot,j); } } swap(array,pivot,left); quicksort(array,left,pivot-1); quicksort(array,pivot+1,right); } }
void quicksort(int array[], int left, int right) { if(left<right){ int pivot=left,j; for(j=left+1;j<=right;j++) if(array[j]<array[left]) swap(array,++pivot,j); swap(array,pivot,left); quicksort(array,left,pivot-1); quicksort(array,pivot+1,right); } }
void quicksort(int *array, int n) { if(n>1){ int pivot=0,j; for(j=1;j<n;j++) if(array[j]<array[0]) swap(array,++pivot,j); swap(array,0,pivot); quicksort(array,pivot); quicksort(array+pivot+1,n-pivot-1); } }这样的话可以把原本20多行的快排缩减到10行,但是这样有什么意义呢,程序的可读性大为下降,而且运行效率也没有丝毫的提升。此外,指针算术很可能会导致各种越界错误。 况且C语言的话,再短也短不了多少了,接下来可以看看快排在其它的语言中的实现,鉴于Java,C#之类的语言实际上和C是一个系列的(都是基于Von-Neuman体系的Imperative Language)。我来展示如何用Declarative Language中来编写quicksort,在这里我使用Scheme(函数式语言)和Python(脚本语言)来演示。
;; define x as value 1 (define x 1) ;; define l as a list of 1 2 3 (define x (list 1 2 3)) ;; define f as a square function (define (f x) (* x x)) ;; define a function use lambda (define f (lambda (x) (* x x))) ;; use a high-order function filter, will return (list 1 2 3) (filter (lambda (x) (<= x 3)) (list 1 2 3 4 5))关于Scheme语言的更多内容可以参考 The little schemer或是网上的specification,在这里就不赘述了。
;; Sort a number list via quicksort algorithm ;; list of numbers -> list of numbers (define (q-sort l) ;;get a number p from l ;;get numbers<=p from l-{p} as small part ;;get number>p from l-{p} as bigger part ;;recursively quicksort on small part and bigger part ;;combine small part, p, bigger part together as the sorted list )
;; Sort a number list via quicksort algorithm ;; list of numbers -> list of numbers (define (q-sort l) ;;get a number p from l (let ((p (first l))) ;;get numbers<=p from l-{p} as small part ;;get number>p from l-{p} as bigger part ;;recursively quicksort on small part and bigger part ;;combine small part, p, bigger part together as the sorted list ) )
;; Sort a number list via quicksort algorithm ;; list of numbers -> list of numbers (define (q-sort l) (cond [(empty? l) empty] [(empty? (rest l)) (list (first l))] [else ;;get a number p from l ;;get numbers<=p from l-{p} as small part ;;get number>p from l-{p} as bigger part (let ((small-part (filter (lambda (x) (<= x (first l))) (rest l))) (big-part (filter (lambda (x) (> x (first l))) (rest l)))) ;;recursively quicksort on small part and bigger part )] ) )
;; Sort a number list via quicksort algorithm ;; list of numbers -> list of numbers (define (q-sort l) (cond [(empty? l) empty] [(empty? (rest l)) (list (first l))] [else ;;get a number p from l ;;get numbers<=p from l-{p} as small part ;;get number>p from l-{p} as bigger part (let ((small-part (filter (lambda (x) (<= x (first l))) (rest l))) (big-part (filter (lambda (x) (> x (first l))) (rest l)))) ;;recursively quicksort on small part and bigger part (append (q-sort small-part) (list (first l)) (q-sort big-part)))] ) )可以发现scheme程序的一大特点就是声明性,你只需告诉它 what to do,而C程序的话则强调 How to do,实际上,上面的程序根本不需要注释,它自己的代码已经足够说明自己的用途了。
;; Sort a number list via quicksort algorithm ;; list of numbers -> list of numbers (define (q-sort l) (cond [(empty? l) empty] [(empty? (rest l)) (list (first l))] [else (let ((small-part (filter (lambda (x) (<= x (first l))) (rest l))) (big-part (filter (lambda (x) (> x (first l))) (rest l)))) (append (q-sort small-part)(list (first l))(q-sort big-part)))] ) )
def q_sort(l): #get first number p from l #move elements<p to the left side #move elements>=p to the right side #recursively quicksort left and right part #combine them together
def q_sort(l): def quicksort(l,left,right): #get first number p from left end #move elements<p to the left side #move elements>=p to the right side #recursively quicksort left and right part #combine them together quicksort(l,0,len(l)-1)
def q_sort(l): def quicksort(l,left,right): if right>left: #get first number p from left end pivot,j,tmp=left,left+1,l[left] #move elements<p to the left side #move elements>=p to the right side while j<=right: if l[j]<tmp: pivot=pivot+1 l[pivot],l[j]=l[j],l[pivot] j=j+1 l[left],l[pivot]=l[pivot],l[left] #recursively quicksort left and right part quicksort(l,left,pivot-1) quicksort(l,pivot+1,right) quicksort(l,0,len(l)-1)python有一个很好用的特性就是 list comprehension,利用这个特性可以写出声明性很强的代码,比如说:
# Get all even numbers between 1 and 100 [x for x in range(1,101) if x%2==1] # Get all line which start with 'From' [line for line in lines if line.startwith('From')]
def q_sort(l): #get first number p from l #move elements<p to the left side #move elements>=p to the right side #recursively quicksort left and right part #combine them together
def q_sort(l): #get first number p from l p=l[0] #move elements<p in l-{p} to the left side small_part=[x for x in l[1:] if x<p] #move elements>=p in l-{p} to the right side big_part=[x for x in l[1:] if x>=p] #recursively quicksort left and right part and combine them together return q_sort(small_part)+[p]+q_sort(big_part)执行程序,oops,进入死循环了。
def q_sort(l): if len(l)<=1: return l else: #get first number p from l p=l[0] #move elements<p in l-{p} to the left side small_part=[x for x in l[1:] if x<p] #move elements>=p in l-{p} to the right side big_part=[x for x in l[1:] if x>=p] #recursively quicksort left and right part and combine them together return q_sort(small_part)+[p]+q_sort(big_part)这次运行结果正常了,需要注意的是, 这个q_sort并不是进行原地排序,而是不断的生成新的list,当list的元素很多时会对性能造成很大的负面影响,这里只是为了演示python的特性才这么写。 如何把它变得更短呢?首先去掉注释试试
def q_sort(l): if len(l)<=1: return l else: p=l[0] small_part=[x for x in l[1:] if x<p] big_part=[x for x in l[1:] if x>=p] return q_sort(small_part)+[p]+q_sort(big_part)去除注释之后的q_sort函数只剩下8行了,还可以更短些吗?答案是肯定的。 注意到q_sort里面用了不少的临时变量,可以把它们去除掉
def q_sort(l): if len(l)<=1: return l else: return q_sort([x for x in l[1:] if x<l[0]])+[l[0]]+q_sort([x for x in l[1:] if x>=l[0]])只剩下5行了!
def q_sort(l): return l if len(l)<=1 else q_sort([x for x in l[1:] if x<l[0]])+[l[0]]+q_sort([x for x in l[1:] if x>=l[0]])现在只剩下2行了,需要注意的是python也提供了lambda表达式,因此q_sort可以被进一步简化:
q_sort= lambda l: l if len(l)<=1 else q_sort([x for x in l[1:] if x<l[0]])+[l[0]]+q_sort([x for x in l[1:] if x>=l[0]])一个Quicksort究竟可以写到多么短呢?上面的代码给出了答案,1行就足够了。
It's best, actually, to learn all five of Python, C/C++, Java, Perl, and LISP. Besides being the most important hacking languages, they represent very different approaches to programming, and each will educate you in valuable ways.Peter Norvig则更加直接[7]:
Learn at least a half dozen programming languages. Include one language that supports class abstractions (like Java or C++), one that supports functional abstraction (like Lisp or ML), one that supports syntactic abstraction (like Lisp), one that supports declarative specifications (like Prolog or C++ templates), one that supports coroutines (like Icon or Scheme), and one that supports parallelism (like Sisal)所以他说才会说 Teach Yourself Programming in Ten Years
[1] The practice of Programming. Brian W Kernighan and Rob Pike. Addison Wisley [2] How to design programs. MIT Press. [3] Learning Python 3rd edition. O'Reilly Press [4] Code complete 2nd edition. Microsoft Press [5] Linguistic Relativity. Wikipedia [6] How to become a hacker. Eric S Raymond [7] Learning programming in Ten Years. Peter Norvig