1. 交換ソート
いわゆる交換とは、シーケンス内の 2 つの要素キーの比較結果に従って、シーケンス内の 2 つのレコードの位置を交換することを指します。為替ベースのソートアルゴリズムは数多くありますが、ここではバブルソートとクイックソートを中心に紹介します。
前回のブログでは挿入ソートを学習しましたが、今日は交換ソートです。挿入ソートと挿入ソートは両方とも内部ソートです。
2. バブルソート
1. 基本的な考え方
バブルソートの基本的な考え方は次のとおりです。隣接する要素の値を後ろから前(または前から後ろ)に 2 つずつ比較し、順序が逆かどうか(つまり、A[i- 1]>A[i])、シーケンスが比較されるまで交換します。これをバブリングの最初のパスと呼び、その結果、最小の要素がソート対象の列の最初の位置に交換されます (または、最大の要素がソート対象の列の最後の位置に交換されます)。最小のキーワードを持つ要素は泡のようなもの 一般に、それは「水面」に達するまで徐々に上に「浮き」ます(または、キーワードの最大の要素が石のように水底に沈みます)。次のバブリングでは、前のバブリングで決定された最小要素は比較に参加しなくなり、各バブリングの結果として、シーケンス内の最小要素 (または最大要素) がシーケンスの最後の位置に配置されます。 .so do n-1 は最大でも 1 回のバブリングですべての要素を並べ替えることができます。
2. 例
以下は、最初のバブリング時のバブルソーティングのプロセスです: 27<49、交換なし; 13<27、交換なし; 76> 13、交換; 97>13、交換; 65>13、交換; 38> 13、交換; 49>13、交換。最初のバブリング パスを通過した後、最小要素は最初の位置に交換され、それが最終位置でもあります。2 番目のバブリング パスでも同じ方法が残りの部分列の並べ替えに使用され、以降も同様に行われます。5 回目のパスの後は交換がなく、テーブルが正常であることを示し、バブル 並べ替えは終了します。
3. パフォーマンス分析
バブルソートのパフォーマンス分析は次のとおりです。
-
スペース効率: 一定数の補助ユニットのみが使用されるため、スペースの複雑さは 0(1) になります。
-
時間効率: 初期シーケンスが正常である場合、最初のバブリング後もフラグがまだ false であることは明らかです (このパスでは要素の交換はありません)。そのため、ループから直接飛び出し、比較の数は n- 1、移動数は 0 であるため、最良のケースです。以下の時間計算量は O(n) です。最初のシーケンスが逆順の場合、n-1 個のソートが必要で、i 番目のソートでは ni を比較する必要があります。キーワードと要素を比較するたびに 3 回移動して、要素の位置を交換する必要があります。この状況では、
比較回数 = ∑ 1 t ( n − 1 ) \sum_1^t (n-1)∑1た( n−1 ) =n ( n − 1 ) 2 \frac{n(n-1)}{2}2n ( n − 1 ),手数 = ∑ 1 t 3 ( n − i ) \sum_1^t 3(ni)∑1た3 ( n−i ) =3 n ( n − 1 ) 2 \frac{3n(n-1)}{2}23 n ( n − 1 )
したがって、最悪の場合の時間計算量は O(n 2 )、平均時間計算量は 0(n 2 ) になります。
安定性: i>j かつ A[i]=A[j] であるため、交換は行われないため、バブルソートは安定したソート方法です。
3. クイックソート
1. 基本的な考え方
クイックソートの基本的な考え方は、分割統治法に基づいています。ソートされるリスト [L1...n] 内で、要素ピボットがピボットとして取得されます (またはベンチマークと呼ばれ、通常は最初の要素が取得されます)。 、ソートされるリストは一方向ソートによって分割されます。独立した 2 つの部分 L[...k-1] と L[k+1...n] の場合、L[...k 内のすべての要素が次のようになります。 -1] がピボット未満、L[k1...n] のすべての要素がピボット以上、ピボットは最終位置 L(k) に配置されます。このプロセスは除算と呼ばれます。次に、各部分に要素が 1 つだけになるか空になるまで、つまりすべての要素が最終位置に配置されるまで、2 つのサブリストに対して上記のプロセスを再帰的に繰り返します。
2. 例
クイックソート処理は、検索と交換を交互に行う処理です。以下に例を紹介します。ポインタ i
と j が 2 つあり、初期値はそれぞれ low と high で、最初の要素 49 をピボットとしますそして変数 pivot に割り当てられます。
-
ポインタ j は上位から前方に検索してピボットより小さい最初の要素 27 を見つけ、27 を i が指す位置に交換します。
-
ポインタ i は下位から逆方向に検索してピボットより大きい最初の要素 65 を見つけ、65 を j が指す位置に交換します。
-
ポインタ j は前方検索を続けてピボットより小さい要素 13 を見つけ、13 を i が指す位置に交換します。
-
同じ方法を使用して、各サブシーケンスをすばやく並べ替えます。並べ替えるシーケンス内に要素が 1 つしかない場合、それは明らかに順序どおりです。
3. パフォーマンス分析
クイックソートアルゴリズムのパフォーマンス分析は次のとおりです。
- スペース効率: クイックソートは再帰的であるため、再帰呼び出しの各層に必要な情報を保存するには再帰作業スタックが必要であり、その容量は再帰呼び出しの最大深さと一致します。最良の場合、それは O(log 2 n) です。最悪の場合、n-1 回の再帰呼び出しが必要であるため、スタックの深さは O(n) です。平均すると、スタックの深さは O(ログ2 n )。
- 時間効率: クイック ソートの実行時間は、パーティションが対称かどうかに関係します。クイック ソートの最悪のケースは、2 つの領域にそれぞれ n-1 個の要素と 0 個の要素が含まれる場合に発生します。この最大の非対称性が各層の再帰で発生すると、つまり、初期ソート テーブルが基本的に順序付けされているか、基本的に逆になっている場合、最悪の場合の時間計算量は 0(n 2 ) です。
- クイックソートは不安定なアルゴリズムです。
4. コアアルゴリズムの実装
1. バブルソート
//冒泡排序
void BubbleSore(SqList &L){
Elemtype temp;
for(int i=0;i<L.length;i++){
//每一趟确定第一个数据的位置
bool flag=false; //每一趟是否发生交换的标志
for(int j=L.length-1;j>i;j--){
//从最后开始冒泡
if(L.data[j-1].grade>L.data[j].grade){
temp=L.data[j];
L.data[j]=L.data[j-1];
L.data[j-1]=temp;
flag=true; //表示发生交换
}
}
if(flag==false)
return;
}
}
2. クイックソート
//划分函数
int Partition(SqList &L,int low,int high){
Elemtype pivot=L.data[low];
while(low<high){
while(low<high && L.data[high].grade>=pivot.grade) high--;
L.data[low]=L.data[high];
while(low<high && L.data[low].grade<=pivot.grade) low++;
L.data[high]=L.data[low];
}
//出这个while循环的条件就是low=high
L.data[low]=pivot;
return low;
}
//快速排序
void QuickSort(SqList &L,int low,int high){
if(low<high){
int pivotpos=Partition(L,low,high); //开始划分
QuickSort(L,low,pivotpos-1); //依次对两个子表进行划分
QuickSort(L,pivotpos+1,high);
}
}
5. C言語表示
/*我们今天的主角插入排序是基于查找算法来的,所以我们还是利用线性表来进行模拟*/
/*为了便于我们后面演示希尔排序,所以我们采用顺序存储结构*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define MaxSize 50 //这里只是演示,我们假设这里最多存五十个学生信息
//定义学生结构
typedef struct {
char name[200]; //姓名
int grade; //分数,这个是排序关键字
} Elemtype;
//声明使用顺序表
typedef struct {
/*这里给数据分配内存,可以有静态和动态两种方式,这里采用动态分配*/
Elemtype *data; //存放线性表中的元素是Elemtype所指代的学生结构体
int length; //存放线性表的长度
} SqList; //给这个顺序表起个名字,接下来给这个结构体定义方法
//初始化线性表
void InitList(SqList &L){
/*动态分配内存的初始化*/
L.data = (Elemtype*)malloc(MaxSize * sizeof(Elemtype)); //为顺序表分配空间
L.length = 0; //初始化长度为0
}
//求表长函数
int Length(SqList &L){
return L.length;
}
//求某个数据元素值
bool GetElem(SqList &L, int i, Elemtype &e) {
if (i < 1 || i > L.length)
return false; //参数i错误时,返回false
e = L.data[i - 1]; //取元素值
return true;
}
//输出线性表
void DispList(SqList &L) {
if (L.length == 0)
printf("线性表为空");
//扫描顺序表,输出各元素
for (int i = 0; i < L.length; i++) {
printf("%s %d", L.data[i].name, L.data[i].grade);
printf("\n");
}
printf("\n");
}
//插入数据元素
bool ListInsert(SqList &L, int i, Elemtype e) {
/*在顺序表L的第i个位置上插入新元素e*/
int j;
//参数i不正确时,返回false
if (i < 1 || i > L.length + 1 || L.length == MaxSize)
return false;
i--; //将顺序表逻辑序号转化为物理序号
//参数i正确时,将data[i]及后面的元素后移一个位置
for (j = L.length; j > i; j--) {
L.data[j] = L.data[j - 1];
}
L.data[i] = e; //插入元素e
L.length++; //顺序表长度加1
return true;
/*平均时间复杂度为O(n)*/
}
//冒泡排序
void BubbleSore(SqList &L){
Elemtype temp;
for(int i=0;i<L.length;i++){
//每一趟确定第一个数据的位置
bool flag=false; //每一趟是否发生交换的标志
for(int j=L.length-1;j>i;j--){
//从最后开始冒泡
if(L.data[j-1].grade>L.data[j].grade){
temp=L.data[j];
L.data[j]=L.data[j-1];
L.data[j-1]=temp;
flag=true; //表示发生交换
}
}
if(flag==false)
return;
}
}
//划分函数
int Partition(SqList &L,int low,int high){
Elemtype pivot=L.data[low];
while(low<high){
while(low<high && L.data[high].grade>=pivot.grade) high--;
L.data[low]=L.data[high];
while(low<high && L.data[low].grade<=pivot.grade) low++;
L.data[high]=L.data[low];
}
//出这个while循环的条件就是low=high
L.data[low]=pivot;
return low;
}
//快速排序
void QuickSort(SqList &L,int low,int high){
if(low<high){
int pivotpos=Partition(L,low,high); //开始划分
QuickSort(L,low,pivotpos-1); //依次对两个子表进行划分
QuickSort(L,pivotpos+1,high);
}
}
int main(){
SqList L;
Elemtype stuents[10]={
{
"张三",649},{
"李四",638},{
"王五",665},{
"赵六",697},{
"冯七",676},
{
"读者",713},{
"阿强",627},{
"杨曦",649},{
"老六",655},{
"阿黄",604}};
//这一部分忘了的请回顾我的相关博客
printf("初始化顺序表并插入开始元素:\n");
InitList(L); //这时是一个空表,接下来通过插入元素函数完成初始化
for (int i = 0; i < 10; i++)
ListInsert(L, i + 1, stuents[i]);
DispList(L);
/*printf("根据分数进行冒泡排序的结果为:\n");
BubbleSore(L);
DispList(L);*/
printf("根据分数进行快速排序的结果为:\n");
int low=0;
int high=L.length-1;
QuickSort(L,low,high);
DispList(L);
}