文章目录
Hello大家好我是开罗小8,今天我来给大家带来如何使用快速排序对背包,排行榜等游戏中常见集合排序,并将其与其他算法进行比较,以及如何对其进行优化。
前言
在游戏领域中,经常会需要使用到排序的功能,例如玩家整理背包后对道具按稀有度,获得日期,ID等进行排序,或者在排行榜中对玩家的得分进行排序。不同的排序算法有不同的适用环境,有的排序在特定情况下排序的速度会更快,但却在另一种情况下不如其他的排序算法。因此我们需要根据不同的情况选择不同的算法。
本篇文章中,使用C#代码,让我们看一下如何将快速排序应用到游戏中。
相关资料
C# List<>.Sort() 排序的底层实现原理_c#list的底层原理-CSDN博客
一、什么是快速排序?
快速排序的定义
快速排序是一种常用的排序算法,时间复杂度为O(nlog n),空间复杂度为O(log n),它是一种不稳定的排序算法,在处理大规模数据时,快速排序的效率是最高的,因此使用最广泛。
快速排序的缺点
不稳定:快速排序是一种不稳定的排序算法,不稳定体现在排序过后,相同值元素在排序前的相对位置可能发生改变,举个例子,比如给一个班的学生按成绩进行排序,小明和小红的分数相同,在排序前,小明在小红的前面,而排序后,小红变到了小明的前面。
特定情况下时间复杂度退化到O(n^2):在待排序集合基本处于有序的情况下,快速排序时间复杂度退化到O(n^2),变成选择排序。
二、快速排序的实现以及优化
我们以对班级学生按成绩进行排序举例
1.基本快排代码
public class Stu
{
public string Name;
public int Score;
public Stu(string name, int score)
{
Name = name;
Score = score;
}
}
class Program
{
static void Main()
{
List<Stu> list = new List<Stu>() {
new Stu("小明", 100), new Stu("小红", 99), new Stu("小刚", 98),
new Stu("小伟", 99), new Stu("小强", 100),
};
QuickSort(list, 0, list.Count - 1);
}
public static void QuickSort(List<Stu> list, int left, int right)
{
if (left >= right) return;
Stu pivot = list[left];
int i = left + 1, j = right;
while (i < j)
{
while (i < j && list[i].Score >= pivot.Score) ++i;
while (i < j && list[j].Score < pivot.Score) --j;
if (i < j)
{
(list[i], list[j]) = (list[j], list[i]);
}
}
if (list[i].Score < pivot.Score) --i;
(list[i], list[left]) = (list[left], list[i]);
QuickSort(list, left, i - 1);
QuickSort(list, i + 1, right);
}
}

(list[i], list[j]) = (list[j], list[i]);为交换i和j的值
上面的排序算法正确的对每一个学生的成绩进行了排序,但是,成绩相同的两位同学的相对位置发生了变化,在排序前,小明排在小强的前面,但是排序后小强排到了小明的前面,有时这并不是我们想要的
2.优化后的排序算法
public class Stu : IComparable<Stu>
{
public string Name;
public int Score;
int ID;
public Stu(string name, int score, int id)
{
Name = name;
Score = score;
ID = id;
}
public int CompareTo(Stu other)
{
if (other.Score != Score) return other.Score - Score;
return ID - other.ID;
}
}
class Program
{
static void Main()
{
List<Stu> list = new List<Stu>() {
new Stu("小明", 100, 1), new Stu("小红", 99, 2), new Stu("小刚", 98, 3),
new Stu("小伟", 99, 4), new Stu("小强", 100, 5),
};
//list.Sort();因为Stu实现了IComparable接口,因此可以直接利用自带的排序方法
QuickSort2(list, 0, list.Count - 1);
}
public static void QuickSort2(List<Stu> list, int left, int right)
{
if (left >= right) return;
Stu pivot = list[left];
int i = left + 1, j = right;
while (i < j)
{
while (i < j && list[i].CompareTo(pivot) <= 0) ++i;
while (i < j && list[j].CompareTo(pivot) > 0) --j;
if (i < j)
{
(list[i], list[j]) = (list[j], list[i]);
}
}
if (list[i].CompareTo(pivot) > 0) --i;
(list[i], list[left]) = (list[left], list[i]);
QuickSort2(list, left, i - 1);
QuickSort2(list, i + 1, right);
}
}
为了保证排序后相对顺序不变,我加入了一个新的字段ID,ID不会重复,排序的规则变成了以分数优先,其次再按ID进行排序,ID大的排在后面
整体的排序思路变为小于等于pivot的元素放在pivot的左边,大于pivot的元素放在右边,CompareTo用于比较两个元素的大小
为什么继承IComparable接口?
继承这个接口后,可以直接利用.net自带的排序方法进行排序,.net自带的方法在排序方面做了更多的优化,效率更高,可以省去自己再写排序的代码的时间。
CompareTo的返回值用于判断大小,如果返回正数,说明比传入的对象大,负数说明小于传入的对象
.net对排序做的优化:
- 在排序的元素较少的情况下,使用插入排序
- 在元素较多时使用内省排序,原理为快速排序+堆排序,这个排序算法首先从快速排序开始,当递归深度超过一定深度(深度为排序元素数量的对数值)后转为堆排序
如何减少快排的最差情况
当数组基本有序时,快排会退化成选择排序,时间复杂度变为O(n^2),因此使用插入排序更合适,插入排序在数组基本有序的情况下的时间复杂度为O(n)
冒泡排序做一下优化也可以在数组基本有序的情况下复杂度为O(n),做法为在每一趟排序结束后做一次判断,如果这一趟排序没有元素发生交换,说明数组已经有序,直接终止排序
如果一定要用快排,可以从以下两方面入手
- 避免数组有序
- 随机选择pivot点
避免数组有序可以使用洗牌算法在排序前先打乱数组,来减少最差情况
随机选取pivot点的实现可以参考文章顶部的相关资料
public static void Shuffle(int[] nums)
{
Random rand = new Random();
int n = nums.Length;
for(int i=0;i<n; ++i)
{
int index=rand.Next(i, n);
(nums[i], nums[index]) = (nums[index], nums[i]);
}
}
总结
本文介绍了快速排序在游戏领域的应用,展示了快速排序不稳定的特点会导致的问题,并提供解决方案,以及最大程度上减少快排退化成O(n^2)的可能性