linlu11 的博客

(转)一个显示排序过程的Python脚本

 

2009-05-08 11:16

之前向大家介绍过《一个排序算法比较的网站》,那个网站用动画演示了各种排序算法,并分析了各种排序算法。这里,要向大家推荐一个Python脚本,其可以把排序的过程给显示出来。

下图是“冒泡排序”的一个示例,其中:

  1. 折线表示了各个元素的位置变化。
  2. 折线的深浅表示了元素的大小。越深则越大。

bubble

同样,还有其它一些排序算法的图片:

堆排序(Heap Sort)

heap

选择排序(Selection)

selection

快速排序(Quick)

quick

Shell排序

shell

插入排序(Insertion)

listinsertion

你可以使用如下的Python代码来制作这些图片:(需要 Cairo图片库支持)

Python排序脚本

这个脚本参数如下:

  • -a 表示使用什么样的算法,取值为"quick", "heap", "selection", "insertion", "bubble", "shell"。
  • -n 表示要排序的数据个数。
  • -f 表示输入文件。
  • -p 表示文件前缀。
  • -d 表示输出顺序。
  • -x 图片宽度。
  • -y 图片高度。
  • -l 所有线的宽度。
  • -b 边界宽度。

使用示例如下:

./visualise.py -l 6 -x 700 -y 300 -n 15

 

 

#!/usr/bin/env python
"""
    Some of these algorithms are all rather literally from Knuth - as a
    consequence they're not very Pythonic, and not terribly readable.

    In some cases I've modified the algorithm to make sure that all items are
    present once and only once in the array of sortables every time we
    memoizePath (i.e. that the algorithm is in-place). 
    
    Page numbers refer to The Art of Computer Programming vol. 3.

    This code is in the public domain - do whatever you like with it.
"""
import random, math, sys
from optparse import OptionParser
import cairo

def intRGB(r, g, b):
        return (r/255.0, g/255.0, b/255.0)

HIGHLIGHT=intRGB(0xff, 0x72, 0x72)

class NiceCtx(cairo.Context):
    defaultBorderColour = intRGB(0x7d, 0x7d, 0x7d)
    def stroke_border(self, border):
        src = self.get_source()
        width = self.get_line_width()
        self.set_source_rgba(*self.defaultBorderColour)
        self.stroke_preserve()
        self.set_source(src)
        self.set_line_width(width - (border * 2))
        self.stroke()


class Canvas:
    def __init__(self, width, height):
        self.width, self.height = width, height
        self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
        self.background(1, 1, 1)

    def ctx(self):
        return NiceCtx(self.surface)

    def background(self, r, g, b):
        c = self.ctx()
        c.set_source_rgb(r, g, b)
        c.rectangle(0, 0, self.width, self.height)
        c.fill()
        c.stroke()

    def save(self, fname):
        self.surface.write_to_png(fname)
            

class Sortable:
    def __init__(self, i):
        self.i = i
        self.path = []

    def __cmp__(self, other):
        return cmp(self.i, other.i)

    def __repr__(self):
        return str(self.i)


class TrackList:
    def __init__(self, itms):
        self.lst = [Sortable(i) for i in itms]

    def __getattr__(self, attr):
        return getattr(self.lst, attr)
    
    def memoizePath(self):
        for i, v in enumerate(self):
            v.path.append(i)
    

class PathDrawer:
    def __init__(self, width, height, line, border, highlights, prefix):
        self.width, self.height = width, height
        self.line, self.border = line, border
        self.highlights, self.prefix = highlights, prefix

    def _lineCoords(self, elem, l):
        init = 0.02 # Proportional initial length 
        lst = []
        xscale = (1.0-init)/len(elem.path)
        yscale = 1.0/l
        lst.append((0, yscale/2 + (yscale * elem.path[0])))
        lst.append((init, yscale/2 + (yscale * elem.path[0])))
        for i, v in enumerate(elem.path):
            lst.append(((xscale * i) + init, yscale/2 + (yscale * v)))
        lst.append((1, lst[-1][1]))
        return lst

    def draw(self, algo):
        c = Canvas(self.width, self.height)
        # Clearer when drawn in this order
        l = reversed(algo.lst)
        ctx = c.ctx()
        for elem in l:
            for i in self._lineCoords(elem, len(algo.lst)):
                ctx.line_to(self.width * i[0], self.height * i[1])
            ctx.set_line_cap(cairo.LINE_CAP_BUTT)
            ctx.set_line_join(cairo.LINE_JOIN_ROUND)
            if elem.i in self.highlights:
                ctx.set_source_rgb(*HIGHLIGHT)
            else:
                x = 1 - (float(elem.i)/len(algo.lst)*0.7)
                ctx.set_source_rgb(x, x, x)
            ctx.set_antialias(cairo.ANTIALIAS_SUBPIXEL)
            ctx.set_line_width(self.line)
            ctx.stroke_border(self.border)
        c.save("%s%s.png"%(self.prefix, algo.name))


class Algorithm:
    def __init__(self, entries):
        self.name = self.__class__.__name__
        self.lst = TrackList(entries)
        self.lst.memoizePath()
        self.sort(self.lst)


class Bubble(Algorithm):
    def sort(self, lst):
        bound = len(lst)-1
        while 1:
            t = 0
            for j in range(bound):
                if lst[j] > lst[j+1]:
                    lst[j], lst[j+1] = lst[j+1], lst[j]
                    lst.memoizePath()
                    t = j
            if t == 0:
                break
            bound = t
    

class ListInsertion(Algorithm):
    """
        Broadly based on the list insertion sort on p 97.  
    """
    def sort(self, lst):
        for i in range(1, len(lst)):
            for j in range(i):
                if lst[i] < lst[j]:
                    x = lst.pop(i)
                    lst.insert(j, x)
                    lst.memoizePath()


class Shell(Algorithm):
    """
        Shell's method, p. 84
    """
    def sort(self, lst):
        t = [5, 3, 1]
        for h in t:
            for j in range(h, len(lst)):
                i = j - h
                r = lst[j]
                flag = 0
                while i > -1:
                    if r < lst[i]:
                        flag = 1
                        lst[i+h], lst[i] = lst[i], lst[i+h]
                        i -= h
                        lst.memoizePath()
                    else:
                        break
                lst[i+h] = r


class Selection(Algorithm):
    """
        Selection Sort, p. 139
    """
    def sort(self, lst):
        for j in range(len(lst)-1, 0, -1):
            m = lst.index(max(lst[:j]))  # No, this is not efficient ;)
            lst[m], lst[j] = lst[j], lst[m]
            lst.memoizePath()


class Heap(Algorithm):
    """
        Algorithm from http://en.wikipedia.org/wiki/Heapsort
    """
    def sift(self, lst, start, count):
        root = start
        while (root * 2) + 1 < count:
            child = (root * 2) + 1
            if child < (count-1) and lst[child] < lst[child+1]:
                child += 1
            if lst[root] < lst[child]:
                lst[root], lst[child] = lst[child], lst[root]
                lst.memoizePath()
                root = child
            else:
                return

    def sort(self, lst):
        start = (len(lst)/2)-1
        end = len(lst)-1
        while start >= 0:
            self.sift(lst, start, len(lst))
            start -= 1
        while end > 0:
            lst[end], lst[0] = lst[0], lst[end]
            lst.memoizePath()
            self.sift(lst, 0, end)
            end -= 1


class Quick(Algorithm):
    """
        http://www.cs.indiana.edu/classes/a348-dger/lectures/tsort/1.0.2/QSortAlgorithm.java
    """
    def sort(self, lst, left=0, right=None):
        if right is None:
            right = len(lst) - 1
        l = left
        r = right
        if l <= r:
            mid = lst[(left+right)/2]
            while l <= r:
                while l <= right and lst[l] < mid:
                    l += 1
                while r > left and lst[r] > mid:
                    r -= 1
                if l <= r:
                    lst[l], lst[r] = lst[r], lst[l]
                    lst.memoizePath()
                    l+=1
                    r-=1
            if left < r:
                self.sort(lst, left, r)
            if l < right:
                self.sort(lst, l, right)



def main():
    usage = "usage: %prog [options]"
    parser = OptionParser(usage)
    parser.add_option(
        "-a",
        dest="algorithm",
        default=False,
        type="choice",
        choices=["quick", "heap", "selection", "insertion", "bubble", "shell"],
        help="Draw only a named algorithm."
    )
    parser.add_option(
        "-n",
        dest="numelements",
        default="20",
        type="int",
        help="Generate a random sorting sequence of length n"
    )
    parser.add_option(
        "-f",
        dest="readfile",
        help="Read data from file"
    )
    parser.add_option(
        "-p",
        dest="prefix",
        help="File name prefix.",
        default=""
    )
    parser.add_option(
        "-d",
        dest="dump",
        default=False,
        action="store_true",
        help="Dump sequence"
    )
    parser.add_option(
        "-x",
        dest="width",
        type="int",
        default=700,
        help="Image width"
    )
    parser.add_option(
        "-y",
        dest="height",
        type="int",
        default=300,
        help="Image height"
    )
    parser.add_option(
        "-l",
        dest="line",
        type="int",
        default=6,
        help="Total line width"
    )
    parser.add_option(
        "-b",
        dest="border",
        type="int",
        default=1,
        help="Border width"
    )
    parser.add_option(
        "-i",
        dest="highlight",
        type="int",
        default=[],
        action="append",
        help="Highlight digit N (0-based). Can be passed muiltiple times."
    )
    options, args = parser.parse_args()
    if args:
        parser.error("Script takes no arguments.")
    if options.readfile:
        txt = file(options.readfile).read().split()
        lst = [int(i) for i in txt]
    else:
        lst = range(options.numelements)
        random.shuffle(lst)
    if options.highlight:
        if max(options.highlight) > (len(lst)-1):
            parser.error("Highlight element > than list length.")
    if options.dump:
        for i in lst:
            print i,
    ldrawer = PathDrawer(
        options.width,
        options.height,
        options.line,
        options.border,
        options.highlight,
        options.prefix
    )
    for i in [Quick, Heap, Selection, ListInsertion, Bubble, Shell]:
        name = i.__name__
        if options.algorithm:
            if not options.algorithm.lower() == name.lower():
                continue
        print >> sys.stderr, name
        a = i(lst)
        ldrawer.draw(a)


if __name__ == "__main__":
    main()

posted on 2009-12-16 22:30  linlu11  阅读(497)  评论(0编辑  收藏  举报

导航