ONE 基本概念
洛谷刷题,写到排序,于是把排序复习了一遍。
排序算法预览:https://www.edrawsoft.cn/viewer/public/s/371d2752967218
排序算法的稳定性:
如果待排序表中有两个元素Ri,Rj,其对应关键字keyi=keyj,且在排序前Ri在Rj前面,如果使用某一排序算法排序后,Ri仍然在Rj的前面,则称这个排序算法是稳定的,否则称排序算法是不稳定的。需要注意,算法是否稳定不能衡量一个算法的优劣,它主要是对算法的性质进行描述。
内部排序:指的是排序期间元素全部存放在内存中的排序。
外部排序:指在排序期间元素无法全部同时存放于内存中,必须在排序过程中根据需求不断在内外存之间移动的排序。
内部排序算法的性能取决于算法的时间复杂度和空间复杂度,而时间复杂度一般由比较和移动的次数来决定。
TWO 插入排序
- 直接插入排序
直接插入排序适用于顺序存储和链式存储的线性表。当表为链式存储时,可以从前往后查找指定元素位置。
注意:大部分排序算法都仅适用于顺序存储的线性表。
//直接插入排序
void InsertSort(vector<int> &buf)
{
int i,j;
int n = buf.size() - 1;
for (i = 2; i <= n; i++) //依次将buf[2]到buf[n]插入到前面的已排序序列
{
if (buf[i] > buf[i - 1]) //若buf[i]小于其前驱,需将buf[i]插入有序表
{
buf[0] = buf[i]; //存入buf[0],(注意,待排序列下标是1 ~ n
for (j = i - 1; buf[0] > buf[j]; --j)//从后往前查找待插入位置
{
buf[j + 1] = buf[j]; //向后挪位
}
buf[j + 1] = buf[0]; //复制到插入位置
}
}
}
- 折半插入排序
因为直接插入排序中有两个操作,查找待插入元素位置和为该元素腾出空间。而查找待插入元素位置的时候查找的表为有序表,因此很容易想到一种优化方式(折半查找)。
注意:折半查找仅适用于有序顺序表,因此折半插入排序只能用于顺序存储的线性表。
//折半插入排序
void InsertSorts(vector<int> &buf)
{
int i, j, low, high, mid;
int n = buf.size() - 1;
for (i = 2; i <= n; i++) //依次将buf[2]到buf[n]插入到前面的已排序序列
{
buf[0] = buf[i]; //将buf[i]暂存入buf[0]
low = 1; high = i - 1; //设置折半查找范围
while (low <= high) //折半查找
{
mid = (low + high) / 2; //取中间点
if (buf[mid] > buf[0])
{
high = mid - 1; //查找左半子表
}
else
{
low = mid + 1; //查找右半子表
}
}
for (j = i - 1; j >= high + 1; --j)
{
buf[j + 1] = buf[j]; //向后挪位
}
buf[j + 1] = buf[0]; //插入
}
}
- 希尔排序
希尔排序又称为缩小增量排序,基本思想是将待排序表分割成若干个子表,分别进行直接插入排序,当整个表中元素已经基本有序时,再对全体记录进行一次直接插入排序。
希尔排序的时间复杂度受到步长变化方式的影响,当前方式n在一定范围内时约为O(n^1.3),最坏情况下是O(n^2)。
//希尔排序
void ShellSort(vector<int> &buf)
{
int i, j;
int n = buf.size() - 1;
for (int dk = n / 2; dk >= 1; dk = dk / 2) //步长变化
{
for (i = dk + 1; i <= n; i++)
{
if (buf[i] > buf[i - dk]) //将buf[i]插入有序增量子表
{
buf[0] = buf[i]; //暂存于buf[0]
for (j = i - dk; j > 0 && buf[0] > buf[j]; j -= dk)
{
buf[j + dk] = buf[j]; //后移元素,查找插入位置
}
buf[j + dk] = buf[0]; //插入
}
}
}
}
THREE 交换排序
- 冒泡排序
就是冒泡,字面意思(@_@)
//冒泡排序
void BubbleSort(vector<int> &buf)
{
int n = buf.size();
for (int i = 0; i < n; i++)
{
bool flag = false; //表示本次冒泡是否发生了交换
for (int j = buf.size() - 1; j > i; j--) //一次冒泡过程
{
if (buf[j - 1] < buf[j]) //若为逆序
{
swap(buf[j - 1], buf[j]); //交换
flag = true; //表示发生了交换
}
}
if (flag = false) //若本次冒泡没有发生交换,则说明表已经有序
{
return;
}
}
}
- 快速排序
快速排序是对冒泡排序的改进,基本思想是分治法。
快速排序是所有内部排序算法中平均性能最优的排序算法。
在待排序列中任选一个元素pivot作为基准,通过一次排序将待排序列分成两部分,使得前一部分所有元素均小于pivot,另一部分所有元素大于pivot。此时pivot已经到达最终位置。这个过程称为一趟快速排序。接下来分别递归地对两个子表重复上述过程,直至每一部分内只有一个元素或空为止。此时所有元素均在其最终位置。
首先写划分算法。当前比较常用的划分操作为:每次选取当前表的第一个元素作为基准值pivot。代码如下:
//快排--划分操作
int Partition(vector<int> &buf, int lows, int highs)
{
int pivot = buf[lows]; //将当前表中第一个元素设为基准值,对表进行划分
while (lows < highs) //循环跳出条件,当所有元素遍历一遍时lows等于highs
{
while (lows < highs&&buf[highs] >= pivot)
{
--highs;
}
buf[lows] = buf[highs]; //将比基准值小的元素移到左端
while (lows < highs&&buf[lows] <= pivot)
{
++lows;
}
buf[highs] = buf[lows]; //将比基准值大的元素移到右端
}
buf[lows] = pivot; //基准值存放到最终位置
return lows; //返回存放基准值的位置作为新表划分位置
}
接下来递归地调用快速排序算法进行排序:
//快速排序
void QuickSort(vector<int> &buf,int low,int high)
{
if (low < high) //若low>=high说明此表只有一个元素或空
{
int pivotpos = Partition(buf, low, high); //划分位置
QuickSort(buf, low, pivotpos - 1); //将划分后的左表进入递归
QuickSort(buf, pivotpos + 1, high); //将划分后的右表进入递归
}
}
FORE
断电了,明天继续

作者在洛谷刷题时复习排序算法,介绍了排序算法的稳定性、内外排序概念。重点讲解插入排序,包括直接插入、折半插入、希尔排序;还介绍交换排序,如冒泡排序和快速排序,快速排序是冒泡排序的改进,是内部排序中平均性能最优的算法。
4677

被折叠的 条评论
为什么被折叠?



