回 帖 发 新 帖 刷新版面

主题:[原创] 排序方法的总结与讨论

DECLARE SUB show (m!())                    '输出排序后的结果
DECLARE SUB CountingSort ()       '计数排序
DECLARE FUNCTION QSSplit! (p!, r!)          '快速排序的关键过程
DECLARE SUB SelectSort ()            '选择排序
DECLARE SUB init ()               '初始化
DECLARE SUB refresh ()          '数组重新赋值以进行下一个排序
DECLARE SUB BubbleSort ()         '冒泡排序
DECLARE SUB InsertSort ()         '插入排序
DECLARE SUB QuickSort (p!, r!)       '快速排序的主过程
CLS
CONST maxn = 15000             '最多有15000个数,考虑到qb的数组也就能开那么大。
CONST maxm = 10000               '数组元素的取值范围是1-10000
DIM SHARED n, a(1 TO maxn), b(0 TO maxn)         'n是元素个数;a存随机生成的、待排序的数;
CALL init          '初始化

REM Simple Sort,简单排序,时间复杂度均为O(n^2)
CALL BubbleSort     
CALL InsertSort
CALL SelectSort

REM Difficult Sort,复杂的。快速排序时间复杂度是O(nlogn),是基于比较的排序法中平均最快的了,强烈推荐
CALL refresh       '因为快速用到了递归,所以只好在子程序外面初始化数组
st# = TIMER          '用来记时
CALL QuickSort(1, n)        '调用快速排序
PRINT "QuickSort:"; TIMER - st#; "s"

CALL CountingSort      '计数排序,时间复杂度是O(m+n),这个超强啊~线性的复杂度~强烈推荐。

SUB BubbleSort
CALL refresh
st# = TIMER
FOR i = 1 TO n - 1
FOR j = i + 1 TO n
  IF b(i) > b(j) THEN SWAP b(i), b(j)
NEXT j
NEXT i
PRINT "BubbleSort:"; TIMER - st#; "s"
'CALL show(b())
END SUB

SUB CountingSort
CALL refresh
DIM c(maxm)
st# = TIMER

FOR i = 1 TO n: c(a(i)) = c(a(i)) + 1: NEXT
FOR i = 2 TO maxm: c(i) = c(i) + c(i - 1): NEXT
FOR i = n TO 1 STEP -1
  b(c(a(i))) = a(i)
  c(a(i)) = c(a(i)) - 1
NEXT i

PRINT "CountingSort:"; TIMER - st#; "s"
'CALL show(b())
END SUB

SUB init
RANDOMIZE TIMER
INPUT "How many numbers:", n
FOR i = 1 TO n
  a(i) = INT(RND * maxm + 1)
'  PRINT a(i);
NEXT
'PRINT
'PRINT
END SUB

SUB InsertSort
CALL refresh
st# = TIMER
FOR i = 2 TO n
  j = i
  WHILE b(j) < b(j - 1)
    SWAP b(j), b(j - 1)
    j = j - 1
  WEND
NEXT i
PRINT "InsertSort:"; TIMER - st#; "s"
'CALL show(b())
END SUB

FUNCTION QSSplit (j, k)
t = INT(RND * (k - j + 1)) + j
x = b(t)
i = j - 1: j = k + 1
WHILE 1
  DO: j = j - 1: LOOP UNTIL b(j) >= x
  DO: i = i + 1: LOOP UNTIL b(i) <= x
  IF i < j THEN
    SWAP b(i), b(j)
  ELSE
    QSSplit = j
    EXIT FUNCTION
  END IF
WEND
END FUNCTION

SUB QuickSort (p, r)
IF p < r THEN
  q = QSSplit((p), (r))
  CALL QuickSort((p), (q))
  CALL QuickSort((q + 1), (r))
END IF
END SUB

SUB refresh
FOR i = 1 TO n
b(i) = a(i)
NEXT
END SUB

SUB SelectSort
CALL refresh
st# = TIMER
FOR i = 1 TO n - 1
  ln = i
  l = b(i)
  FOR j = i + 1 TO n
    IF b(j) < l THEN
      l = b(j)
      ln = j
    END IF
  NEXT j
  SWAP b(i), b(ln)
NEXT i
PRINT "SelectSort:"; TIMER - st#; "s"
'CALL show(b())
END SUB

SUB show (m())
FOR i = 1 TO n
  PRINT m(i);
NEXT i
END SUB

大家运行一下,输入的n要在1-10000之内,不然冒泡之类的简单排序能等死你。
数据是随机生成的。

可以看到,复杂排序(快速和计数)要比简单排序快N倍,但同样,它们费空间。
快速排序用栈空间实现递归,而计数排序要求元素的取值范围已知且不是很大。

其实,复杂排序还有很多,例如归并排序,堆排序,桶排序(线性),基数排序(线性),都是很强的,只不过我都没有掌握,惭愧啊。

大家看看,有问题可以问。程序不懂的也可以提出。

我就先擅自给自己加精了,并临时置顶几天。如果另外两位认为有问题,可以取消。

回复列表 (共22个回复)

21 楼

不是很看得懂哦。

22 楼

[color=800000]补充一下这个称为快速排序的思想根据。只是从我的文化水平小学程度的角度来说说,希望高手们别见笑。[/color]

[color=800000]也有很多朋友都已经知道关于“深度优先搜索”“二叉树排序”之类的名词了,不过很抱歉,我水平还没到那个程度,我倒是知道在数据结构导论里面有解释的,但我还没看到那几页,我花了五年的工夫才看了第6页,(自考的准考证又要过期了。)但建议大家应该多学习教材上的知识,不应该像我这样孤陋寡闻。[/color]


[color=800000]这个排序的依据其实并不太复杂的,我记得以前是看懂了的,但这几年脑袋退化了很多,所以都记不起来了。[/color]

[color=800000]1.首先是分段思想,把总的数组随机找一个节点r,以r为界线把数组分成两段[/color]
[color=800000]2.把这个节点的值p当成一个中间值,然后用i,j两个指针从两边向中间扫描。[/color]
[color=800000]3.交换值的目的是把所有小于中间值p的数据放在左边,所有大于中间值p的数据放去右边[/color]
[color=800000]4.当两个指针相交的时候,以i为边界,就已经完成了第一次的两边分段,左边就已经全是小于p的数据了,右边就已经全是大于p的数据了。[/color]
[color=800000]5.再先后拿一段数据来同样处理,依次类推,一直到段首段末相邻的时候再跑回来处理下一段,这样全部段都处理完了,序 也就排好了。[/color]

[color=800000]来看看标准的程序结构:[/color]

SUB QuickSort (L, h)
IF L < h THEN              '[color=0000FF]对某段数据排序[/color]
   IF h - L = 1 THEN       '[color=0000FF]如果上下界是相邻的,比较一下也就完事了[/color]
      IF S(L) > S(h) THEN SWAP S(L), S(h)
   ELSE
      r=INT(RND*(h-L+1))+L '[color=0000FF]设置一个中间节点[/color]
      SWAP S(h), S(r)      '[color=0000FF]把它和最后的元素对换,因为它的位置还不能确定的,要到扫[/color]
      p = S(h)             '[color=0000FF]描完毕才能确定,所以得先把它放最后去。[/color]
      i = L                '[color=0000FF]记录这个中间节点的值p以便对数据作比较[/color]
      j = h                '[color=0000FF]设置两端的指针[/color]
         DO                
            DO WHILE (i < j) AND (S(i) <= p)    '[color=0000FF]左边扫描,找到一个不应该在左边的大于[/color]
                 i = i + 1                      '[color=0000FF]中间值p的数据元素为止[/color]
            LOOP
            DO WHILE (j > i) AND (S(j) >= p)    '[color=0000FF]右边扫描,找到一个不应该在右边的小于[/color]
                 j = j - 1                      '[color=0000FF]中间值p的数据元素为止[/color]
            LOOP
            IF i < j THEN SWAP S(i), S(j)   '[color=0000FF]这两个指针还没交叉相遇的话就交换它们之间[/color]
                                            '[color=0000FF]的值,这样子,小于中间值p的数据就弄到左[/color]
                                            '[color=0000FF]边去了,大于中间值p的数据就弄到右边去了,[/color]
                                            '[color=0000FF]要注意的是,这个时候,中间作为分界的点还[/color]
                                            '[color=0000FF]没有确定的。[/color]
         LOOP WHILE i < j           '[color=0000FF]指针相遇的时候就已经完成两边大小的分隔了。[/color]
         SWAP S(i), S(h)            '[color=0000FF]这个时候位置i就是两边的分界点,也就是中间值p应有[/color]
                                    '[color=0000FF]的位置了。其实把p的值放在这个位置已经是最终位置[/color]
                                    '[color=0000FF]了,之后都不再需要移动的了。因为左右两边的大小[/color]
                                    '[color=0000FF]都是拿它来作对比对照的,大的在右边,小的在左边,[/color]
                                    '[color=0000FF]它站的当然就已经是它应该站的位置了。[/color]
         IF (i - l) < (h - i) THEN  '[color=0000FF]这一句的目的是先把短的一段先处理,这样子可以省一[/color]
            QuickSort L, i - 1      '[color=0000FF]点递归调用时堆栈的耗用,也是快者优先的一个表现,[/color]
            QuickSort i + 1, h      '[color=0000FF]玩QQ上的连连看也是说要先消近的容易的先嘛,同样道[/color]
         ELSE                       '[color=0000FF]理喽。[/color]
            QuickSort i + 1, h      '[color=0000FF]大小已经分好在两边了,只要对某边同样道理处理就可[/color]
            QuickSort L, i - 1      '[color=0000FF]以了,因为属于这一边的数据已经全弄过来了,对它们[/color]
         END IF                       '[color=0000FF]自己再次排序就可以了,[/color]
      END IF
   END IF
END SUB

[color=008080]其实应该不难理解了。好比是一支军队,以一米六为界限,把士兵分到两个师去[/color]
[color=008080]  然后每个师又拿一个高度作为界线,把士兵分成两个旅。[/color]
[color=008080]  然后每个旅又拿一个高度作为界线,把士兵分成两个团。[/color]
........
到了剩两个人的最小的组了,你俩自己按高低排一排,那么这个军队的队也就都排好了。

举例:s(10):  10,9,8,7,6,5,4,3,2,1           
               |                    |     |
               L         选一个随机位置r  h

把节点位置r上的值放到下界去:
      s(10):  10,9,8,7,6,5,4,1,2,3
开始扫描:     |                          |         p=3
               i                          j

s(10):  10,9,8,7,6,5,4,1,2,3           
         |                       |
         i                       j         

s(10):   2,9,8,7,6,5,4,1,10,3           
            |                 |
            i                 j         

s(10):   2,1,8,7,6,5,4,9,10,3           
               |           |
               i           j  这个j指的值一直大于3,可以一直跑了

s(10):   2,1,8,7,6,5,4,9,10,3           
               |\
               i j  两个指针相遇了

s(10):   2,1,3,7,6,5,4,9,10,8
               |\   把节点与指针对换
               i j  把那个节点的中间值放到它应在的位置上去

到了这里已经完成排序子程的主要任务了
中间节点值3已经在它的最终位置上了
小于它的数已经全放它的左边去了
大于它的数已经全放它的右边去了
剩下的工作就是把两边都作为单独的段
按同样的方法去处理就可以完成排序了

         

我来回复

您尚未登录,请登录后再回复。点此登录或注册