基于MoreWindows整理的基础排序的个人理解
来源:互联网 发布:康迪与知豆哪个好 编辑:程序博客网 时间:2024/06/04 18:01
春节期间拜读了MoreWindows整理的白话经典算法之七大排序,受益匪浅。但正所谓纸上得来终觉浅,这两天一一重新写了一遍,将一些心得体会记录如下:
一、关于排序算法的稳定性:一个稳定的排序算法,就是相同值的左右位置经过排序之后不会更换。一个很好的例子是:一个学生的信息包含学号,年龄,按照年龄来进行由小到大的排序,最终学号的顺序也还是从小到大的。七种算法里面,冒泡,插入,归并排序是稳定的,而希尔、快速、选择、堆排序是不稳定的。
二、几种常用排序算法小结,完全的自我理解:
1.冒泡排序:
一个基于左右比较的排序算法,发现跟预定的规则不一致就交换,然后紧接着进行左右比较,然后一直进行下去,每一趟可以确定一个最大数,或者最小数,如果说进行一次从小到大的比较,而最大的元素恰好是第一个,那么第一趟的操作看起来就是第一个元素不断的向上滚动,类似于冒泡了。
从实现上看,外层循环最大为元素的个数,内存循环就是重复的左右比较。但是无论是外层循环,还是内层比较循环都是优化的空间:先说内层循环,最后发生交换的位置的后面都是有序的,所以下次左右比较的最后位置就是这里,如果一次比较过程都没有发生交换,那么说明目前的列表已经是有序的了,外层的循环也就可以终止了。
由于基于左右比较的,值相同的两个元素的左右位置自然不会发生交换,也就是稳定的了,基于冒泡排序的其他算法自然也是稳定的,比较插入排序。
两层循环的变量之前没有关联,虽然冒泡排序的时间复杂度为0(n^2),但是这种算法的思路,代码实现都很简单,特别是对于原始算法的优化的思考比较有意思,知道了如何实现还不够,只有对实现进行了理解才能优化,优化之后本身也会加深
基于pascal的代码:
procedure TAlgorithmTest.BubbleOrder(const ASourceData: TIntegerDynArray; const IsAsc: Boolean);var LastFlag, CurrentFlag: Integer; I: Integer;begin LastFlag := High(ASourceData); while LastFlag > 0 do begin CurrentFlag := LastFlag; LastFlag := 0; for I := 1 to CurrentFlag do begin if NeedSwap(ASourceData[I - 1], ASourceData[I], IsAsc) then begin SwapData(ASourceData, I - 1, I); LastFlag := I - 1; end; end; end;end;2 插入排序
算法的基本思路:如果一个列表是有序的,每插入一个元素进行排序,那么子列表始终是有序的,当子列表的元素跟原始列表一样那就能保证整个列表已经处于有序的状态了。那么,这样一来就有了:外层循环从第二个元素开始(单个元素是有序的),自循环就是将后面的元素插入到当前的子列表中,子列表就在原始列表的前端,所以需要反向遍历查找位置,插入新的数值。所以,两层循环的变量是有关联关系的。如何维护子列表有三种思路:
2.1 对于新元素,先反向遍历之前的元素,找到合适的位置,然后直接将该元素移动那个位置
procedure TAlgorithmTest.InsertOrigin(const ASourceData: TIntegerDynArray; const IsAsc: Boolean);var I, J, K: Integer; TempVal: Integer;begin for I := Succ(Low(ASourceData)) to High(ASourceData) do begin J := I - 1; while (j >= 0) do begin if IsAsc then begin if ASourceData[j] < ASourceData[I] then Break; end else begin if ASourceData[j] > ASourceData[I] then Break; end; Dec(J) end; if j <> I - 1 then begin TempVal := ASourceData[I]; k := I - 1; while(k > j) do begin ASourceData[k + 1] := ASourceData[k]; Dec(k); end; ASourceData[k + 1] := TempVal; end; end;end;2.2 对于新元素,直接反向遍历子列表,然后找到合适的位置,将数值插入进去,相对于2.1,就是将查找位置和移动元素的工作结合起来了
procedure TAlgorithmTest.InsertOptimize( const ASourceData: TIntegerDynArray; const IsAsc: Boolean);var I, J, k: Integer; TempValue: Integer;begin for I := Succ(Low(ASourceData)) to High(ASourceData) do begin if IsAsc then begin if (ASourceData[I] < ASourceData[I - 1]) then begin TempValue := ASourceData[I]; K := I - 1; while(k >= 0) and (ASourceData[k] > TempValue) do begin ASourceData[K + 1] := ASourceData[k]; Dec(k); end; ASourceData[K + 1] := TempValue; end; end else begin if (ASourceData[I] > ASourceData[I - 1]) then begin TempValue := ASourceData[I]; k := I - 1; while(K >= 0) and (ASourceData[k] < TempValue) do begin ASourceData[K + 1] := TempValue; Dec(k); end; ASourceData[K + 1] := TempValue; end; end; end;end;2.3 当前的元素尝试在子列表中冒泡一趟
procedure TAlgorithmTest.InsertOrder(const ASourceData: TIntegerDynArray; const IsAsc: Boolean);var I, J: Integer;begin for I := Succ(Low(ASourceData)) to High(ASourceData) do begin J := I - 1; while(j >= 0) and NeedSwap(ASourceData[J + 1], ASourceData[J], IsAsc) do begin SwapData(ASourceData, J + 1, J); Dec(J); end; end;end;
插入排序的代码实现其实是”反向冒泡“,所以,插入排序是稳定的。
插入算法思路很有意思,具体的3种实现方式各有特点,同时也是希尔排序的基础。
3 希尔排序
希尔排序是基于不断分组排序的一种算法,分组中的排序实现是使用插入排序实现。由于需要分组,所以,这种排序算法本身就是不稳定的。
希尔排序也有三种实现方法:
3.1 严格的按照定义:不断的分组,组间进行插入排序
procedure TAlgorithmTest.ShellOrder(const ASourceData: TIntegerDynArray; const AIsAsc: Boolean);var DataCount: Integer; Gap: Integer; GroupIndex: Integer; MemIndex: Integer; TempValue: Integer; K: Integer;begin DataCount := Length(ASourceData); Gap := DataCount div 2; while (Gap > 0) do begin for GroupIndex := 0 to Pred(Gap) do begin MemIndex := GroupIndex + Gap; while (MemIndex < DataCount) do begin if AIsAsc then begin if ASourceData[MemIndex] < ASourceData[MemIndex - Gap] then begin TempValue := ASourceData[MemIndex]; k := MemIndex - Gap; while(k >= GroupIndex) and (ASourceData[k] > TempValue) do begin ASourceData[k + Gap] := ASourceData[k]; k := k - Gap; end; ASourceData[k + Gap] := TempValue; end; end else begin if ASourceData[MemIndex] > ASourceData[MemIndex - Gap] then begin TempValue := ASourceData[MemIndex]; k := MemIndex - Gap; while(k >= GroupIndex) and (ASourceData[k] < TempValue) do begin ASourceData[k + Gap] := ASourceData[k]; Dec(k); end; ASourceData[k + Gap] := TempValue; end; end; MemIndex := MemIndex + Gap; end; end; Gap := Gap div 2; end;end;3.2 相对于3.1,其实是一个裁剪代码的过程,确定了一个gap之后,从gap开始,然后增量是gap其实跟上面代码的含义一样
procedure TAlgorithmTest.ShellOrder_InterGroup( const ASourceData: TIntegerDynArray; const AIsAsc: Boolean);var DataCount: Integer; Gap: Integer; MemIndex: Integer; TempValue: Integer; K: Integer;begin DataCount := Length(ASourceData); Gap := DataCount div 2; while(Gap > 0) do begin MemIndex := Gap; while (MemIndex < DataCount) do begin if AIsAsc then begin if ASourceData[MemIndex] < ASourceData[MemIndex - Gap] then begin TempValue := ASourceData[MemIndex]; k := MemIndex - Gap; while(k >= 0) and (ASourceData[k] > TempValue) do begin ASourceData[k + Gap] := ASourceData[k]; k := k - Gap; end; end; end; MemIndex := MemIndex + Gap; end; Gap := Gap div 2; end;end;
3.3 借鉴插入排序的直接反向冒泡,更加精简代码
procedure TAlgorithmTest.ShellOrder_InterGroupSwap( const ASourceData: TIntegerDynArray; const AIsAsc: Boolean);var DataCount: Integer; Gap: Integer; MemIndex: Integer; k: Integer;begin DataCount := Length(ASourceData); Gap := DataCount div 2; while(Gap >= 0) do begin MemIndex := Gap; while(MemIndex < DataCount) do begin k := MemIndex; while (k >= 0) do begin if NeedSwap(ASourceData[MemIndex], ASourceData[MemIndex - Gap], AIsAsc) then SwapData(ASourceData, MemIndex, MemIndex); k := k - Gap; end; MemIndex := MemIndex + Gap; end; Gap := Gap div 2; end;end;
4 选择排序
选择排序的思路就是按照排序的规则,找到当前位置的元素。思路很简洁,代码也很简单。
由于是选择,交换,所以这个过程中元素的顺序被打乱了,不再是一个左右比较交换,而是一个全局的比较交换。
外层控制了当前的位置,内层循环就是从当前位置的下个位置到结束的一个查找交换,所以,内外层有关系。
procedure TAlgorithmTest.SelectOrder(const ASourceData: TIntegerDynArray; const IsAsc: Boolean);var I, J, DesiredIndex: Integer;begin for I := Low(ASourceData) to Pred(High(ASourceData)) do begin DesiredIndex := I; for J := I + 1 to High(ASourceData) do begin if IsAsc then begin if ASourceData[DesiredIndex] > ASourceData[J] then DesiredIndex := J; end else begin if ASourceData[DesiredIndex] < ASourceData[J] then DesiredIndex := J; end; SwapData(ASourceData, I, DesiredIndex); end; end;end;
5 归并排序
归并排序和快速排序使用了“分而治之”的思路,“分而治之”本身包含了由整体到部分的转换,包含一种迭代的思想在里面。这里的“分”是指将一个原始序列分为小块,就是从中间切,“治”就是一个将当前“分”的部分合起来,不断返回迭代的结果,最终保证了整个数据列是有需要的。
由于这种“分”是左右分,“治”也是按照两两比较,没有出现颠倒的数据元素,所以,这种排序是有序的。另外,由于这种算法的的实现在“治”的过程中需要申请额外的空间,所以,归并算法的效率比快速排序还高,应该有一种用空间换时间的概念在里面。
procedure TAlgorithmTest.MergeOrder(const ASourceData: TIntegerDynArray; const AIsAsc: Boolean);var Temp: TIntegerDynArray; procedure OrderPart(const ALow, AMid, AHigh: Integer); var i, j, k: Integer; begin i := ALow; j := AMid + 1; k := 0; while(i <= AMid) and (j <= AHigh) do begin if AIsAsc then begin if ASourceData[i] < ASourceData[j] then begin Temp[k] := ASourceData[i]; Inc(k); Inc(i); end else begin Temp[k] := ASourceData[j]; Inc(k); Inc(j); end; end else begin if ASourceData[i] > ASourceData[j] then begin Temp[k] := ASourceData[i]; Inc(k); Inc(i); end else begin Temp[k] := ASourceData[j]; Inc(k); Inc(j); end; end; end; while(i <= AMid) do begin Temp[k] := ASourceData[i]; Inc(k); Inc(i); end; while(j <= AMid) do begin Temp[k] := ASourceData[j]; Inc(k); Inc(j); end; for i := 0 to Pred(k) do begin ASourceData[ALow + i] := Temp[i]; end; end; procedure LoopOrder(const ALow, AHigh: Integer); var MidLocal: Integer; begin if ALow < AHigh then begin MidLocal := (ALow + AHigh) div 2; LoopOrder(ALow, MidLocal); LoopOrder(MidLocal + 1, AHigh); OrderPart(ALow, MidLocal, AHigh); end; end;begin SetLength(Temp, Length(ASourceData)); LoopOrder(Low(ASourceData), High(ASourceData)); Temp := nil;end;
6 快速排序
快速排序也使用了“分而治之”的思路,只是这种思路跟归并算法不一样。
快速排序的分是指,将一个数据列总是按照指定一个元素的方式,然后先从后往前找最小的,然后再从左往右找最大的一次填充,当左指针有有指针碰头了,然后再依据指针碰头的位置将数据列分成两个新的数据列,一次进行下去。
由于快速排序也是一种全局查找的算法,不再强调左右的元素位置,也是不稳定的算法。
procedure TAlgorithmTest.QuickOrder(const ASourceData: TIntegerDynArray; const AIsAsc: Boolean); procedure QuickLoop(const ALow, AHigh: Integer); var Seed, SeedIndex: Integer; Left, Right: Integer; begin if ALow < AHigh then begin Seed := ASourceData[ALow]; Left := ALow; Right := AHigh; SeedIndex := ALow; while(Left < Right) do begin While(Right > Left) and(ASourceData[Right] > Seed) do begin Dec(Right); end; if Right > Left then begin ASourceData[SeedIndex] := ASourceData[Right]; SeedIndex := Right; Dec(Right); end; while(Right > Left) and(ASourceData[Left] < Seed) do begin Inc(Left); end; if Right > Left then begin ASourceData[SeedIndex] := ASourceData[Left]; SeedIndex := Left; Inc(Left); end; end; ASourceData[SendIndex] := Seed; QuickLoop(ALow, Left - 1); QuickLoop(Left + 1, AHigh); end; end;begin QuickLoop(Low(ASourceData), High(ASourceData));end;
7 堆排序
对排序使用的是堆的特性来排序的,并不是将原始数据在内存中组织成一个对象。
这里的堆,是二叉堆。二叉堆的定义:父节点的键值总是大于或者等于任何一个子节点的键值,同时,每个节点的左子树和右子树也是二叉堆。
同时最小树是指任何节点的键值都小于其自己点的键值。
用到的特性:1)最小树根节点的键值最小 2)叶子集结点本身符合二叉堆的定义
用到的方式是下沉,下沉不关心本次的值,只会影响到之前的堆结构。
procedure TAlgorithmTest.MinHeapOrder(const ASourceData: TIntegerDynArray; const AIsAsc: Boolean); procedure FixdownHeap(const StartIndex: Integer; const MaxNodeCount: Integer); var I, J: Integer; Temp: Integer; begin if StartIndex < 0 then begin Exit; end; I := StartIndex; J := I * 2 + 1; while(j < MaxNodeCount) do begin //Try to find the min node from left and right. if ((j + 1) < MaxNodeCount) and (ASourceData[j + 1] < ASourceData[j]) then begin Inc(j); end; ASourceData[i] := ASourceData[j]; I := j; j := I * 2 + 1; end; ASourceData[i] := Temp; end; procedure MakeMinHeap; var I: Integer; Datalen: Integer; begin Datalen := Length(ASourceData); //只需要从非叶子节点下沉即可 for I := Datalen div 2 - 1 downto 0 do begin FixdownHeap(I, Datalen); end; end; procedure SwapData(const AFirstIndex, ASecondIndex: Integer); var Temp: Integer; begin if AFirstIndex = ASecondIndex then begin Exit; end; if ASourceData[AFirstIndex] = ASourceData[ASecondIndex] then begin Exit; end; Temp := ASourceData[AFirstIndex]; ASourceData[AFirstIndex] := ASourceData[ASecondIndex]; ASourceData[ASecondIndex] := Temp; end; procedure ReverseData; var Datalen: Integer; I: Integer; begin Datalen := Length(ASourceData); for I := 0 to Datalen div 2 do begin SwapData(I, Datalen - I - 1); end; end;var k: Integer;begin MakeMinHeap; for k := High(ASourceData) downto 1 do begin SwapData(k, 0); FixdownHeap(0, k); end; if AIsAsc then ReverseData;end;由于堆排序真正是通过移动节点达到排序的目的,不关注实际的数据的先后顺序,本身是不稳定的。
- 基于MoreWindows整理的基础排序的个人理解
- 排序算法的个人理解
- 关于排序的总结和部分实现(个人理解整理版)
- 个人整理的sql语句--基础
- 个人整理的一些java开发基础
- 关于sort排序的个人理解
- 常见排序原理(基于个人理解)
- 个人对于冒泡排序和选择排序的理解
- 自学android的路线(纯属个人理解整理,勿喷)
- 基于扩展欧几里得的证明的个人理解
- 关于几种排序的个人整理及分析
- NVT 的个人整理
- 插入排序-参考MoreWindows
- Design By Contract 基于契约设计的个人理解
- RBAC基于角色的权限控制个人理解
- 基于ocfs2内核代码的个人理解(一)
- 基于unity射线的个人理解(一)
- 插入运算排序的"代码理解"(个人笔记)
- mysql用一个表数据替换另一个表中的数据
- [C/C++标准库]_[初级]_[使用时间库]
- myEclipse对spring的xml编写时添加提示方法
- NYOJ-135 取石子(二) (巴什博奕 + 尼姆博弈)
- armv6 armv7 armv7s架构的区别
- 基于MoreWindows整理的基础排序的个人理解
- cadence 器件按照分页进行编号
- velocity判断对象是否为空
- android 滑动按钮
- Eclipse中文字体太小
- 怎么改远程服务器密码
- 历届试题 打印十字图
- Python学习系列十四:python的内置函数
- 判断表是否存在