8种排序算法的C#实现
排序是将一个数据元素(或记录)的任意序列,重新排列成一个按关键字有序的序列。排序根据涉及的存储器的不同分为内部排序和外部排序:内部排序是指待排序记录存放在内存进行的排序过程;外部排序是指待排序记录的数量很大,以致内存一次不能容纳全部记录,在排序过程中尚需对外存进行访问的排序过程。本文仅讨论内部排序。
1.直接插入排序
最简单的排序方法,基本操作是将一个记录插入到已排好序的有序表中,从而得到一个新的、记录数增1的有序表。
此处为了程序的通用,没有采取数组0位作为哨兵位的做法。
C#实现:
public static int[] StraightInsertionSort(int[] arr)
{
int temp;
for (int i = ; i < arr.Length; i++)
{
if (arr[i] < arr[i - ])
{
//将arr[i]元素存入temp
temp = arr[i];
int j = i; //将i左侧比i大的元素右移一位
do
{
arr[j] = arr[j - ];
j--;
} while (j > && temp < arr[j - ]); //将temp存入比i大的最小元素的左侧位置
arr[j] = temp;
} /*上边实现方法的简化写法,但需要多判断一次,因为初始时j==i==1>0
int j = i;
while (j > 0 && arr[j] < arr[j - 1])
{
temp = arr[j];
arr[j] = arr[j - 1];
arr[j - 1] = temp;
j--;
}
*/
}
return arr;
}
直接插入排序
2.希尔排序
又称“缩小增量排序”,也是一种属插入排序类的方法,但在时间效率上较直接插入排序有较大的改进。
基本思想是先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。
C#实现:
public static int[] ShellSort(int[] arr)
{
int temp;
//初始步长
int gap = arr.Length / ;
while (gap != )
{
#region 改进版的直接插入排序
for (int i = gap; i < arr.Length; i++)
{
if (arr[i] < arr[i - gap])
{
temp = arr[i];
int j = i; do
{
arr[j] = arr[j - gap];
j -= gap;
} while (j - gap >= && temp < arr[j - gap]); arr[j] = temp;
}
}
#endregion //缩小增量
gap /= ;
}
return arr;
}
希尔排序
3.冒泡排序
它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。
C#实现:
public static int[] BubbleSort(int[] arr)
{
int temp;
//每趟排序将无序元素中的最小元素换到有序元素的右侧
for (int i = ; i < arr.Length - ; i++)
{
for (int j = arr.Length - ; j > i; j--)
{
if (arr[j - ] > arr[j])
{
temp = arr[j - ];
arr[j - ] = arr[j];
arr[j] = temp;
}
}
}
return arr;
}
冒泡排序
4.快速排序
对冒泡排序的一种改进,基本思想是,通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
快速排序是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短。
C#实现:
public static int[] QuickSort(int[] arr)
{
return QuickSort(arr, , arr.Length - );
} /// <summary>
/// 递归快速排序
/// </summary>
/// <param name="arr">目标数组</param>
/// <param name="low">数组起始位置</param>
/// <param name="high">数组终止位置</param>
/// <returns></returns>
private static int[] QuickSort(int[] arr, int low, int high)
{
if (low < high)
{
//将目标数组一分为二,得到枢轴位置
int pivotLoc = Partition(arr, low, high);
//对低子表递归排序
QuickSort(arr, low, pivotLoc - );
//对高子表递归排序
QuickSort(arr, pivotLoc + , high);
}
return arr;
} private static void Swap(ref int index, ref int indexReplace)
{
int temp = index;
index = indexReplace;
indexReplace = temp;
} /// <summary>
/// 一趟快排
/// </summary>
/// <param name="arr">目标数组</param>
/// <param name="low">数组起始位置</param>
/// <param name="high">数组终止位置</param>
/// <returns></returns>
private static int Partition(int[] arr, int low, int high)
{
//用子表的第一个记录作枢轴记录
int pivotKey = arr[low];
//从表的两端交替地向中间扫描
while (low < high)
{
//将比枢轴记录小的记录交换到低端
while (low < high && arr[high] >= pivotKey)
{
--high;
}
arr[low] = arr[high];
//将比枢轴记录大的记录交换到高端
while (low < high && arr[low] <= pivotKey)
{
++low;
}
arr[high] = arr[low];
}
//枢轴记录到位
arr[low] = pivotKey;
return low;
}
快速排序
5.简单选择排序
一趟简单排序的操作为:通过n-i次关键字间的比较,从n-i+1个记录中选出关键字最小的记录,并和第i(1<=i<=n)个记录交换。
C#实现:
public static int[] SimpleSelectionSort(int[] arr)
{
int temp;
//每趟排序将无序元素中的最小元素换到有序元素的右侧
for (int i = ; i < arr.Length; i++)
{
int min = i;
for (int j = i + ; j < arr.Length; j++)
{
if (arr[j] < arr[min])
{
min = j;
}
}
temp = arr[i];
arr[i] = arr[min];
arr[min] = temp;
}
return arr;
}
直接选择排序
6.堆排序
堆排序是一种树形选择排序,是对直接选择排序的有效改进。
初始时把要排序的n个数的序列看作是一棵顺序存储的二叉树(一维数组存储二叉树),调整它们的存储序,使之成为一个堆,将堆顶元素输出,得到n 个元素中最小(或最大)的元素,这时堆的根节点的数最小(或者最大)。然后对前面(n-1)个元素重新调整使之成为堆,输出堆顶元素,得到n 个元素中次小(或次大)的元素。依此类推,直到只有两个节点的堆,并对它们作交换,最后得到有n个节点的有序序列。
C#实现:
private static int heapSize;
public static int[] HeapSort(int[] arr)
{
heapSize = arr.Length;
//构建最大堆
for (int i = arr.Length / ; i > ; i--)
{
BuildMaxHeap(arr, i);
}
//将堆顶元素放入堆底的有序序列,并重新调整最大堆
for (int i = arr.Length - ; i > ; i--)
{
//每次在构建好最大堆后,将第一个元素arr[0]和最后一个元素arr[i]交换
swapHeap(arr, , i);
//此时arr[i]为堆的最大值,所以对arr[0]到arr[i - 1]元素进行调整
heapSize--;
//每次新组成的堆除了根节点其他的节点都保持最大堆的特性,因此只要再以根节点为基准调整就可以得到新的最大堆
BuildMaxHeap(arr, );
}
return arr;
} //构建最大堆:调整当前节点和子节点使当前节点为最大元素
private static void BuildMaxHeap(int[] arr, int index)
{
int largeIndex = index;
//当前节点的左子节点
int leftChildIndex = index << ;
//当前节点的右子节点
int rightChildIndex = (index << ) + ;
//左子节点存在且左子节点大于当前节点
if (leftChildIndex <= heapSize && arr[leftChildIndex - ] > arr[largeIndex - ])
{
largeIndex = leftChildIndex;
}
//右子节点存在且右子节点大于当前节点
if (rightChildIndex <= heapSize && arr[rightChildIndex - ] > arr[largeIndex - ])
{
largeIndex = rightChildIndex;
}
if (index != largeIndex)
{
swapHeap(arr, index - , largeIndex - );
BuildMaxHeap(arr, largeIndex);
}
} //互换堆中的两个值
private static void swapHeap(int[] arr, int index, int indexReplace)
{
int temp = arr[index];
arr[index] = arr[indexReplace];
arr[indexReplace] = temp;
}
堆排序
7.归并排序
C#实现:
public static int[] MergeSort(int[] arr)
{
//在整个排序过程中始终使用同一个暂存数组,空间利用率高
int[] tempArr = new int[arr.Length];
return MergeSort(arr, tempArr, , arr.Length - );
} /// <summary>
/// 将目标数组循环折半,递归执行归并排序核心,再组合成有序数组
/// </summary>
/// <param name="arr">目标数组</param>
/// <param name="tempArr">暂存数组</param>
/// <param name="first">子表的起始位置</param>
/// <param name="last">子表的终止位置</param>
/// <returns></returns>
private static int[] MergeSort(int[] arr, int[] tempArr, int first, int last)
{
if (first < last)
{
int mid = (first + last) / ;
MergeSort(arr, tempArr, first, mid);
MergeSort(arr, tempArr, mid + , last);
MergeSortCore(arr, tempArr, first, mid, last);
}
return arr;
} /// <summary>
/// 归并排序核心:将两个有序的左右子表(以mid区分),合并成一个有序表
/// </summary>
/// <param name="arr">目标数组</param>
/// <param name="tempArr">暂存数组</param>
/// <param name="first">子表的起始位置</param>
/// <param name="mid">子表的划分位置</param>
/// <param name="last">子表的终止位置</param>
/// <returns></returns>
private static int[] MergeSortCore(int[] arr, int[] tempArr, int first, int mid, int last)
{
//左侧子表的起始位置
int indexA = first;
//右侧子表的起始位置
int indexB = mid + ;
int tempIndex = ;
//遍历左右子表,直到其中一个表遍历完
while (indexA <= mid && indexB <= last)
{
//左子表的最小元素 <= 右子表的最小元素
if (arr[indexA] <= arr[indexB])
{
//左子表的最小元素暂存数组,遍历左子表下标+1
tempArr[tempIndex++] = arr[indexA++];
}
else
{
tempArr[tempIndex++] = arr[indexB++];
}
}
//有一侧子表遍历完后,将另一侧子表剩下的数一次放入暂存数组中,暂存数组保持有序
while (indexA <= mid)
{
tempArr[tempIndex++] = arr[indexA++];
}
while (indexB <= last)
{
tempArr[tempIndex++] = arr[indexB++];
}
//将暂存数组中有序的元素写回到目标数组的指定位置,使进行归并的数组段有序
tempIndex = ;
for (int i = first; i <= last; i++)
{
arr[i] = tempArr[tempIndex++];
}
return arr;
}
归并排序
8.基数排序
基数排序是借助“分配”和“收集”两种操作对单逻辑关键字进行排序的一种内部排序方法。例如,若关键字是数值,且其值都在0<=K<=999范围内,则可把每一个十进制数字看成一个关键字,即可认为K由3个关键字(Kº,K¹,K²)组成,其中Kº是百位数,K¹是十位数,K²是个位数。
C#实现:
public static int[] RadixSort(int[] arr)
{
//数组中数字的最大位数
int maxDigit = ; //查找最大位数
foreach (int item in arr)
{
int tempDigit = item.ToString().Length;
if (tempDigit > maxDigit)
{
maxDigit = tempDigit;
}
} for (int i = ; i < maxDigit; i++)
{
int[] tempArr = new int[arr.Length];
int[] countArr = new int[]; for (int j = ; j < arr.Length; j++)
{
//数组中每个元素在i位上的数字 = 该元素在<=i位的数字 - 该元素在<=(i-1)位的元素
int splitNum = (int)(arr[j] / Math.Pow(, i)) - (int)(arr[j] / Math.Pow(, i + )) * ;
//累加每个元素在i位上的数字出现的次数,如12,118,92三个数字组成的数组,则当i==1(每个数字的十位)时,countArr[1] == 2, countArr[9] == 1,其余为0
countArr[splitNum]++;
} //累加在i位上出现j及比j小的数字的次数
for (int j = ; j < ; j++)
{
countArr[j] += countArr[j - ];
} //从后向前遍历数组,根据每个元素i位上的数字(splitNum),得到比该数字小或等于该数字的元素个数(countArr[splitNum]),即为该元素在排序后数组中的位置(splitNumIndex)
for (int j = arr.Length - ; j >= ; j--)
{
int splitNum = (int)(arr[j] / Math.Pow(, i)) - (int)(arr[j] / Math.Pow(, i + )) * ;
int splitNumIndex = countArr[splitNum] - ;
tempArr[splitNumIndex] = arr[j]; countArr[splitNum]--;
}
Array.Copy(tempArr, arr, tempArr.Length);
}
return arr;
}
基数排序
总结:各排序算法的稳定性,时间、空间复杂度比较
附:上述代码的调用示例
static void Main(string[] args)
{
int[] arr = { , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , };
int[] arrResult = Sort.RadixSort(arr);
for (int i = ; i < arrResult.Length; i++)
{
Console.Write(arrResult[i]);
if (i < arrResult.Length - )
{
Console.Write(",");
}
}
Console.Read();
}
参考资料:八大排序算法 数据结构(C语言版)
8种排序算法的C#实现的更多相关文章
- 几种排序算法的学习,利用Python和C实现
之前学过的都忘了,也没好好做过总结,现在总结一下. 时间复杂度和空间复杂度的概念: 1.空间复杂度:是程序运行所以需要的额外消耗存储空间,一般的递归算法就要有o(n)的空间复杂度了,简单说就是递归集算 ...
- 秒杀9种排序算法(JavaScript版)
一:你必须知道的 1> JS原型 2> 排序中的有序区和无序区 3> 二叉树的基本知识 如果你不知道上面三个东西,还是去复习一下吧,否则,看下面的东西有点吃力. 二:封装丑陋的原型方 ...
- PHP的几种排序算法的比较
这里列出了几种PHP的排序算法的时间比较的结果,,希望对大家有所帮助 /* * php 四种排序算法的时间与内置的sort排序比较 * 3000个元素,四种算法的排序所用的时间比较 * 冒泡排序 85 ...
- 学习Java绝对要懂的,Java编程中最常用的几种排序算法!
今天给大家分享一下Java中几种常见的排序算法的Java代码 推荐一下我的Java学习羊君前616,中959,最后444.把数字串联起来! ,群里有免费的学习视频和项目给大家练手.大神有空时也 ...
- C#常用8种排序算法实现以及原理简介
public static class SortExtention { #region 冒泡排序 /* * 已知一组无序数据a[1].a[2].--a[n],需将其按升序排列.首先比较a[1]与a[2 ...
- 排序—时间复杂度为O(n2)的三种排序算法
1 如何评价.分析一个排序算法? 很多语言.数据库都已经封装了关于排序算法的实现代码.所以我们学习排序算法目的更多的不是为了去实现这些代码,而是灵活的应用这些算法和解决更为复杂的问题,所以更重要的是学 ...
- java算法03 - 常用的8种排序算法
Java常用的八种排序算法: 插入排序 - 直接插入排序 每次将待排序的记录按照关键字的大小,插入到前面已经排好序的记录的适当位置.直到全部记录插入完成. 代码实现 /** * 直接插入排序 O(n^ ...
- 用 C 语言描述几种排序算法
排序算法是最基本且重要的一类算法,本文基于 VS2017,使用 C 语言来实现一些基本的排序算法. 一.选择排序 选择排序,先找到数组中最小的元素,然后将这个元素与数组的第一个元素位置互换(如果第一个 ...
- 【C++】四种排序算法的时间比较
四种排序算法的时间比较 [注]clock函数对输入(用户输入)元素N排序的计时 #include<iostream> #include<time.h> using namesp ...
- 几种排序算法及Java实现排序的几种方式
几种排序算法 下面的例子介绍了4种排序方法: 冒泡排序, 选择排序, 插入排序, 快速排序 package date201709.date20170915; public class SortUtil ...
随机推荐
- Oracle 表的常见操作
--创建表并指定表空间 create table goods( id VARCHAR2(20) primary key, name VARCHAR2(20) NOT NULL, price NUMBE ...
- js实现车轮的来回滚动
最近喜欢用js做车轮的来回滚动,简单的js动画分享给大家.有什么建议记得说出来大家一起讨论哦!效果图如下: 源代码: <style> #pic1{ width:20px; height:2 ...
- [置顶] Firefox OS 学习——简单了解知识
什么是Firefox OS ? Firefox OS 是一个为网页设计而生的能编译和独立的手机网页操作系统,我们相信在接下来的时代,网页应用将充满整个新兴操作设备,这也为当前许多网页开发者不需要太多的 ...
- H264源码分析(二)
原文出自http://blog.csdn.net/xfding/article/details/5476763(转载收集) (四)图像参数集语义 pic_parameter_set_rbsp( ) { ...
- 滚动栏范围位置函数(SetScrollRange、SetScrollPos、GetScrollRange、GetScrollPos)
滚动栏的范围是一对整数,默认情况下,滚动栏的范围是0~100. SetScrollRange(hwnd,iBar,iMin,iMax,bRedraw)这里的iBar參数要么是SB_VERT,要么是SB ...
- swift http请求返回json数据和分析
1 AppDelegate.swift // // AppDelegate.swift // QQDemo // // Created by 赵超 on 14-6-21. // Copyright ( ...
- jvm莫名退出问题解决
当jvm莫名退出,没有留下任何任何信息的时候,在centos的 /var/log/dmesg文件中,或许可以找到一些端倪
- filestream 读取视频文件
class Program { static void Main(string[] args) { string source = @"F:\361\android studio\4.0\亲 ...
- SSR服务端一键安装脚本
支持新协议混淆,SSR服务端一键安装脚本 Shadowsocks-R 是项目 shadowsocks 的增强版,用于方便地产生各种协议接口.实现为在原来的协议外套一层编码和解码接口,不但可以伪装成 ...
- python基础:名称空间与作用域
Python的变量定义后都有自己的作用域,每个作用域内都有名字空间.名称空间就是变量名称与对象的关联关系.Python中使用变量名引用对象,需要使用该变量时,就在命名空间中进行搜索,获取对应的对象.从 ...