排序
排序的基本概念与术语
- 排序:把一组记录按照某个域值的递增或递减次序重新排列的过程
- 主关键字:能唯一标识某一记录的关键字
- 稳定性:具有相同关键字的记录,排序前后保持它们原来的相对次序不变,则称该排序过程具有稳定性
- 内部排序:排序过程都在内存中进行的排序
- 外部排序:排序过程需要在内存和外存之间不断交换数据的排序
插入排序
直接插入排序
- 思想:将一个记录插入到已经排好序的有序表中,从而得到一个新的记录增一的有序表
- 编程实现
public static void StraightInsertSort<T>(T[] keys) where T : IComparable<T>
{
for(int i = 1;i < keys.Length; i++)
{
T current = keys[i];
int j = i - 1;
while (j >= 0 && keys[j].CompareTo(current) > 0)
{
keys[j + 1] = keys[j];
j--;
}
keys[j + 1] = current;
}
}
static void Main(string[] args)
{
int[] nums = new int[] { 45, 34, 78, 12, 34, 32, 29, 64 };
Sort.StraightInsertSort(nums);
foreach(int num in nums)
{
Console.WriteLine(num);
}
}
//
12
29
32
34
34
45
64
78
希尔插入排序
- 思想:把记录按下标的一定增量分组,对每组使用直接插入排序法,随着增量逐渐减少,所分组包含的记录越来越多,到增量值减至1时,整个记录集被分成一组,算法终止。(先将整个待排序列分割成为若干子序列,分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序)
- 操作:(1).选择一个增量序列(2).按增量序列个数k,对序列进行k趟排序(3).每趟排序,根据对应的增量,将待排序列分割成若干长度为m的子序列,分别对各子表进行直接插入排序,当增量因子为1时,整个序列作为一个表来处理,表长度为整个序列的长度
- 大的往后移动,小的往前移动
- 编程实现
private static void Shell<T>(int delta,T[] keys) where T : IComparable<T>
{
for(int i = delta;i < keys.Length; i++)
{
T current = keys[i];
int j = i - delta;
while(j >= 0 && keys[j].CompareTo(current) > 0)
{
keys[j + delta] = keys[j];
j -= delta;
}
keys[j + delta] = current;
}
}
public static void ShellInsertSort<T>(T[] keys)where T : IComparable<T>
{
for(int delta = keys.Length / 2;delta > 0;delta = delta / 2)
{
Shell(delta, keys);
}
}
static void Main(string[] args)
{
int[] nums = new int[] { 45, 34, 78, 12, 34, 32, 29, 64 };
Sort.ShellInsertSort(nums);
foreach(int num in nums)
{
Console.WriteLine(num);
}
}
//
12
29
32
34
34
45
64
78
选择排序
直接选择排序
- 思想:每一趟在
n-i
个记录中选取关键字最小的记录作为有序序列中第i个记录。(在要排列的一组数中,选出最小的一个数与第1个位置的数交换;然后在剩下的数当中再找最小的与第2个位置的数交换,依此类推,直到第n-1个元素和第n个元素比较为止)
- 代码实现
public static void StraightSelectSort<T>(T[] keys)where T : IComparable<T>
{
for(int i = 0;i < keys.Length - 1; i++)
{
T current = keys[i];
int k = i;//记录最小值位置
for(int j = i + 1;j < keys.Length; j++)
{
if(keys[j].CompareTo(current) < 0)
{
current = keys[j];
k = j;
}
}
if(k != i)
{
T temp = keys[i];
keys[i] = current;
keys[k] = temp;
}
}
}
static void Main(string[] args)
{
int[] nums = new int[] { 45, 34, 78, 12, 34, 32, 29, 64 };
Sort.StraightSelectSort(nums);
foreach(int num in nums)
{
Console.WriteLine(num);
}
}
//
12
29
32
34
34
45
64
78
堆排序
- 堆排序是以重树形选择排序,是对直接选择排序的有效改进
- 堆的概念:具有n个元素的序列K={k1,k2,…,kn},当且仅当满足
k1<=k2i,ki<=k2i+1
或者k1>=k2i,ki>=k2i+1
(树形的左右孩子节点)i=1,2,...,[n/2]
时称之为堆 - 若关键码的集合K={k1,k2,…,kn},把它们按照完全二叉树的顺序存放在一维数组中,若满足
k1<=k2i,ki<=k2i+1
称为小根堆,若满足k1>=k2i,ki>=k2i+1
称为大根堆 - 小根堆与大根堆的例子
- 思想:以构建大根堆为例,构建大根堆之后,输出堆顶记录,对剩余的n-1个记录接着构建大根堆,便可得到n个记录的次大值,如此反复执行,就能得到一个有序序列,这个过程称为堆排序(注:编号要从1开始)
- 堆排序需要解决的问题:
- 问题1:如何构建堆,对初始序列建堆的过程就是一个反复进行筛选的过程,(1).n个结点的完全二叉树,则最后一个结点是第[n/2]个结点的子树.(2).筛选从第[n/2]个结点为根的子树开始,该子树成为堆.(3).之后向前依次对各结点为根的子树进行筛选,使之成为堆,直到根结点(即把序号为[n/2],[n/2]-1,…1的记录作为根的子树调整为堆)
- 问题2:输出堆顶元素后,进行调整新堆
- 代码实现
private static void Restore<T>(T[] keys,int j,int vCount) where T : IComparable<T>
{
while(j <= vCount / 2)
{
int index = 2 * j + 1 <= vCount && keys[2 * j + 1].CompareTo(keys[2 * j]) > 0 ? 2 * j + 1 : 2 * j;
if(keys[j].CompareTo(keys[index]) < 0)
{
T temp = keys[j];
keys[j] = keys[index];
keys[index] = temp;
j = index;
}
else
{
break;
}
}
}
public static void HeapSelectSort<T>(T[] keys) where T : IComparable<T>
{
int vCount = keys.Length;
T[] keys1 = new T[vCount + 1];
for (int i = 0; i < vCount; i++)
{
keys1[i + 1] = keys[i];//序号从1开始
}
for (int i = vCount / 2; i >= 1; i--)
{
Restore(keys1, i, vCount);
}
for (int i = vCount; i >= 2; i--)
{
T temp = keys1[1];
keys1[1] = keys1[i];
keys1[i] = temp;
Restore(keys1, 1, i - 1);//从顶部实现
}
for(int i = 0;i < vCount; i++)
{
keys[i] = keys1[i + 1];//再换回去
}
}
static void Main(string[] args)
{
int[] nums = new int[] { 45, 34, 78, 12, 34, 32, 29, 64 };
Sort.HeapSelectSort(nums);
foreach(int num in nums)
{
Console.WriteLine(num);
}
}
//
12
29
32
34
34
45
64
78
交换排序
冒泡排序
- 通过相邻记录之间的比较和交换,使关键字较小的记录如气泡一般逐渐向上漂移直至水面
- C#代码实现
public static void BubbleExchangeSort<T>(T[] keys) where T : IComparable<T>
{
for(int i = 0;i < keys.Length - 1; i++)
{
for(int j = keys.Length - 1;j > i; j--)
{
if(keys[j].CompareTo(keys[j - 1]) < 0)
{
T temp = keys[j];
keys[j] = keys[j - 1];
keys[j - 1] = temp;
}
}
}
}
public static void BubbleExchangeSortImprove<T>(T[] keys) where T : IComparable<T>
{
for (int i = 0; i < keys.Length - 1; i++)
{
bool flag = false;
for (int j = keys.Length - 1; j > i; j--)
{
if (keys[j].CompareTo(keys[j - 1]) < 0)
{
T temp = keys[j];
keys[j] = keys[j - 1];
keys[j - 1] = temp;
flag = true;
}
}
if (flag == false)
break;//利用flag进行改进看是否已经排好了序
}
}
- Java代码实现
快速排序
- 基本思想:(1).选择一个基准元素,通常选择第一个元素或者最后一个元素。(2).通过一趟排序将待排序的记录分割成独立的两部分,其中一部分记录的元素值均比基准元素值小,另一部分记录的元素值比基准值大。(3).此时基准元素在其排好序的正确位置。(4).然后分别对这两部分记录用同样的方法继续进行排序,直到整个序列有序,即:从待排记录中选一记录,将其放入正确的位置,然后以该位置为界,对左右两部分再做快速排序,直到划分的长度为1
ex1
int[] Key = new int[]{45,34,78,12,34,32,29,64};
- 先从
45
为基准值开始确定45
应处的位置,i
指向45
,j
指向64
,因为64
大于45
所以j
往前移动指向29
,29
小于45
,j
停止,观察34
发现45
大于34
所以i
右移动,移动到78
发现比45
大,因为我们确定基准元素所在的位置后以该位置为界左边是小于基准元素的区间右边是大于基准元素的区间,而i
所处的值78
大于45
,j
所处的值29
小于45
,所以78
和29
要交换位置
{45,34,29,12,34,32,78,64}
- 之后j指向
78
继续前移到32
发现32
比45
小停止移动i
往后移12
比45
小,再往后34
比45
小,在往后移i
与j
相遇,所以就保证了i之前的位置比45
小,i``之后的位置比45
大,所以找到45
所在的位置,并将32
与45
交换得到数组为
{32,34,29,12,34,45,78,64}
- 这样就得到了两个序列,
45
的左边32,34,29,12,34
比45
小,45
的右边78,64
比45
大,之后再对这两个序列继续进行快速交换排序,利用递归思想,对于左边的序列32,34,29,12,34
利用同样的方法以32
为基准元素,i
指向32,j
指向34
,34
比基准元素大,j
前移,发现12
比基准元素小,j
停止,i
往后移,34
比32
大,所以i
停止,交换34
和12
,重复刚才的过程就确定了这个序列利用快排后的结果是29,12,32,34,34
在以基准元素32
为界分成两个区间29,12
和34,34
,再对29,12
进行快排后得到基准元素29
的位置为12,29
再以29
为界限左边只有一个元素右边没有元素,这样就确定好了,再去确定右边的区间34,34
这里i
指向第一个34
,j
指向第二个34
,因为j
所指向的元素不比34
大所以j
停止,i
向后移动发现34<=34
,所以i
向后移动,就找到了基准元素34
(第一个34
)应处的位置,再分为两部分左边只有一个元素右边没有元素,就排好序了,之后对78,64
采取同样的方法即可(递归思想注意顺序)
,最后就可以得到排好序的数组 - 步骤图
- 编程实现
public static void QuickExchangeSort<T>(T[] keys)where T : IComparable<T>
{
QuickSort(keys, 0, keys.Length - 1);
}
private static void QuickSort<T>(T[] keys,int left,int right) where T : IComparable<T>
{
if(left >= right)
{
return;
}
int i = left;
int j = right;
T current = keys[left];
while(i < j)
{
while(keys[j].CompareTo(current) > 0 && i < j)
{
j--;
}//j前移
while(keys[i].CompareTo(current) <= 0 && i< j)
{
i++;
}//i后移
if(i < j)
{
T temp = keys[i];
keys[i] = keys[j];
keys[j] = temp;
}//发生i指向的位置比基准元素大,j指向的位置比基准元素小,需要交换元素
}
keys[left] = keys[j];
keys[j] = current;//找到基准元素所在的位置进行交换
if (j - 1 > left) QuickSort(keys, left, j - 1);//分成左右两个区间继续进行快速交换排序
if (j + 1 < right) QuickSort(keys, j + 1, right);
}
static void Main(string[] args)
{
int[] nums = new int[] { 45, 34, 78, 12, 34, 32, 29, 64 };
Sort.QuickExchangeSort(nums);
foreach(var num in nums)
{
Console.WriteLine(num);
}
}
//
12
29
32
34
34
45
64
78
合并排序
- 思想:把两个或多个有序序列合并成一个有序序列
- 代码实现
public static T[] MergeSort<T>(T[] keys1, T[] keys2) where T : IComparable<T>
{
T[] result = new T[keys1.Length + keys2.Length];
int i = 0, j = 0, k = 0;
while (i < keys1.Length && j < keys2.Length)
{
if (keys1[i].CompareTo(keys2[j]) < 0)
{
result[k] = keys1[i];
i++;
}
else
{
result[k] = keys2[j];
j++;
}
k++;
}
while (i < keys1.Length)
{
result[k] = keys1[i];
k++;
i++;
}
while(j < keys2.Length)
{
result[k] = keys2[j];
k++;
j++;
}
return result;
}
对于一个无序序列如何利用合并排序进行实现呢?
答案:使用递归思想,首先将无序序列利用二分方式进行打散,之后再合并起来,并把排好序的序列赋给原数组即可
public static void MergeSort<T>(T[] keys) where T : IComparable<T>
{
MergeSort(keys, 0, keys.Length - 1);
}
private static void MergeSort<T>(T[] keys,int left,int right) where T : IComparable<T>
{
if (left >= right)
return;
int mid = (left + right) / 2;
MergeSort(keys, left, mid);//向左切
MergeSort(keys, mid + 1, right);//对序列进行打散
MergeSort(keys, left, mid, right);
}
private static void MergeSort<T>(T[] keys,int left,int mid,int right)where T : IComparable<T>
{
T[] result = new T[right - left + 1];
int i = left;
int j = mid + 1;//第二个序列的头
int k = 0;
while(i <= mid && j <= right)
{
if(keys[i].CompareTo(keys[j]) < 0)
{
result[k] = keys[i];
i++;
}
else
{
result[k] = keys[j];
j++;
}
k++;
}
while(i <= mid)
{
result[k] = keys[i];
i++;
k++;
}
while(j <= right)
{
result[k] = keys[j];
j++;
k++;
}
for(int n = 0;n < result.Length; n++)
{
keys[left + n] = result[n];//把排序好的赋回去
}
}
static void Main(string[] args)
{
int[] nums = new int[] { 45, 34, 78, 12, 34, 32, 29, 64 };
Sort.MergeSort(nums);
foreach(var num in nums)
{
Console.WriteLine(num);
}
}
//
12
29
32
34
34
45
64
78