前言
本文是我大二学习数据结构时,根据自己的学习和理解而写的总结(主要是面临期末给自己总结一下,加深理解)
什么是排序?!
- 就是把所有元素按照递增或递减有序排列。
- 内排序?外排序?排序过程中放在内存中处理,若不涉及内外存交换就是内排序;反之外排序。
- 稳定性?不稳定性?排序前后具有相同关键字的次序不变,这种排序方法就是稳定的;反之不稳定的。
排序中的一些有关定义(我本人是真的不喜欢记这些什么东西,感觉每个人有每个人的理解,没必要规定死,但是为了统一所以才有的规定吧):
- 元素序列:直白的说就是,一个数组a[10],a[0],a[1]...中0,1,2...就是所谓的元素序列。
- 关键字:每一个元素序列存放的值(a[0]=k,k就是关键字)
- 正序和反序:待排序元素的关键字顺序和排序后的顺序一样就是正序。待排序的元素关键字和排序后的顺序正好相反就是反序。
- 有序区和无序区:已经排好顺序的区域就是有序区,还未排序的区域就是无序区。
一、内排序
-
直接插入排序
直接插入排序(Straight Insertion Sort)的基本原理是将待排序的元素分为有序和无序两个部分,开始时有序表中只包含1个元素,无序表中包含有n-1个元素。排序过程中每次从无序表中取出第一个元素,将它插入到有序表中的适当位置,使之成为新的有序表,重复n-1次可完成排序过程。
网上随便搜的一个定义
但是我自己是不喜欢记定义的
按我自己的理解就是:
例如一个长度为5的数组[3,7,1,-1,4]递增排序
首先把3取出作为初始元素(也就是第一个元素)
接着从7开始依次和7前面的数比较
第一次插入后的顺序3,7
第二次插入后的顺序1,3,7
依次类推就是:-1,1,3,7
-1,1,3,4,7
代码实现(java)
public void InserSort(){ RecType tmp; //RecType 一个存放元素的类 int j; for(int i = 1; i < n; i ++){ if(R[i].key < R[i-1].key){ tmp = R[i]; j = i - 1; do{ R[j + 1] = R[j];//元素依次后移 j --; }while(j >=0 && R[j].key < R[j-1].key); R[j + 1] = tmp;//后移截止后,把一开始的元素插入 } } }
算法分析:
1>最好情况:初始元素序列为正序,元素不用移动只用比较,则有最小值的比较次数和移动次数,即C =
= n - 1 = O(n), M = 0。
2>最坏情况:初始元素序列为反序,C = n(n-1)/2 = O(
), M = (n-1)(n+4)/2 = O(
)
3>平均情况:C = n(n-1)/4 =O(
), M =
= O(
)
4>空间复杂度为O(1)
5>是一种稳定的排序算法
-
折半插入排序
折半插入排序(Binary Insertion Sort)是一种插入排序算法,它的基本原理是将折半查找方法与直接插入排序方法相结合。在每一次插入新元素时,它利用折半查找方法找到其待插入的位置。
具体来说,折半插入排序的运行过程如下:
- 将待排序序列的第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
- 从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。在查找元素的适当位置时,采用了折半查找方法。如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。
过程如下图:
直白点就是,和直接插入排序类似,但是在插入过程中,直接插入是依次往前一一比较,但是折半插入是利用二分的方法进行比较。
代码实现(java):
public void BinInsertSort(){
int l, r, mid;
RecType tmp;
for(int i = 1; i < n; i ++){
if(R[i].key < R[i-1].key){
tmp = R[i];
l = 0; r = i - 1;
while(l <= r){//利用二分查找合适插入点
mid = l + r >> 1;//(l + r) / 2;
if(R[mid].key > tmp.key) r = mid - 1;
else l = mid + 1;
}
for(int j = i - 1; j >= r + 1; j --) //后移元素
R[j+1] = R[j];
R[r + 1] = tmp;//插入元素
}
}
}
算法性能:
1>平均关键字比较次数:
(i+1) - 1
2>平均移动次数为:i / 2 + 2
3>平均复杂度:O(
)
4>空间复杂度为O(1)
5>一种稳定的排序算法
-
希尔排序
希尔排序原理:
将待排序文件分成若干组,所有距离为gap的记录分在同一组内,并对每一组内的记录进行排序。然后取gap/2的数进行排序,重复上述分组和排序的工作。当到达gap=1时,所有记录在统一组内排好序。
算法实现(java):
public void ShellSort(){
RecType tmp;
int d = n / 2;//增量初始化
while(d > 0){
for(int i = d; i < n; i ++){//采用直接插入进行排序
int j = i - d;
tmp = R[i];
while(j >=0 && R[j].key > tmp.key){
R[j + d] = R[j];
j -= d;
}
R[j + d] = tmp;
}
d /= 2;//递减增量
}
}
算法性能:
一般认为平均复杂度为:O()
空间复杂度O(1)
不稳定 排序算法
-
冒泡排序
冒泡排序的基本原理是:对于未排序的序列,从第一个元素开始,比较相邻的两个元素,如果顺序错误就交换位置。重复这个过程,直到没有需要交换的元素为止。
具体来说,冒泡排序的步骤如下:
- 比较相邻的元素。如果第一个比第二个大(或小),就交换他们两个。
- 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
- 针对所有的元素重复以上的步骤,除了最后一个。
- 持续每次针对剩下的元素重复上面的步骤,直到没有任何一对数字需要比较。
简单的就是2个for循环然后比较相邻元素,直到没有元素需要交换为止。
代码实现(java):
public void BubbleSort(){
boolean exchange;
for(int i = 0; i < n - 1; i ++){
exchange = false;
for(int j = n - 1; j > i; j --){
if(R[j].key < R[j - 1].key){
RecType tmp = R[j];
R[j] = R[j - 1];
R[j - 1] = tmp;
exchange = true;
}
}
if(!exchange) break;
}
}
算法性能:
1>最好情况:(初始序列为正序)C =
= n - 1 = O(n),M = 0
2>最坏情况:(初始序列为反序)C = n(n-1) / 2= O(
), M = 3n(n-1) / 2 = O(
)
3>平均时间复杂度:O(
),空间复杂度O(1),是一种稳定的排序算法
-
快速排序
原理:
快速排序采用的是分治思想,即在一个无序的序列中选取一个任意的基准元素pivot,利用pivot将待排序的序列分成两部分,前面部分元素均小于或等于基准元素,后面部分均大于或等于基准元素,然后采用递归的方法分别对前后两部分重复上述操作,直到将无序序列排列成有序序列。
说人话就是:上面的好像已经够人话了
代码实现(java):
public void QuickSort() { QuickSort1(0,n-1); } public void QuickSort1(int s,int t){ if(s < t){ int i = Partition(s,t); QuickSort1(s,i-1); QuickSort1(i+1,t); } } public int Partition(int s, int t) { RecType tmp; RecType base = R[s]; int i = s, j = t; while(i < j){ while(i < j && R[j].key >= base.key) j--; while(i < j && R[i].key <= base.key) i ++; if(i < j){ tmp = R[i]; R[i] = R[j]; R[j] = tmp; } } tmp = R[s]; R[s] = R[i]; R[i] = tmp; return i; }
算法性能:
最好情况:初始数据随机分布且每次划分都是2个长度相同的子表,每层的时间为O(n)
递归树高度为
(n+1) 最终的时间复杂度为O(n
)
最坏情况:初始数据为正序或反序,递归树高度为O(n),每层的时间为O(n),最终时间复杂度为O(
)
平均:时间复杂度:O(n
),空间复杂度:O(
)
-
简单选择排序
原理:第一趟,从 n 个元素中找出关键字最小的元素与第一个元素交换
第二趟,在从第二个元素开始的 n-1 个元素中再选出关键字最小的元素与第二个元素交换第 k 趟,则从第 k 个元素开始的 n-k+1 个元素中选出关键字最小的元素与第 k 个元素交换
算法实现(java):
public void SelectSort(){
for(int i = 0; i < n; i ++){
RecType tmp;
int k = i;
for(int j = i + 1; j < n; j ++)
if(R[j].key < R[i].key) k = j;
if(k!=i){
tmp = R[k];
R[k] = R[i];
R[i] = tmp;
}
}
}
算法性能:
1>比较次数C = n(n-1) / 2 = O(
),无论数据如何分布比较次数是一样的
2>空间复杂度 O(1)
3>移动次数:最好时(正序):0,最坏(反序):3(n-1),
4>最好、最坏、平均的时间复杂度都是O()
-
堆排序
堆排序(Heap Sort)的原理是利用堆这种数据结构所设计的一种排序算法。堆排序可以分为两个主要步骤:建堆和排序。建堆的目的是将原始数组构建成一个大顶堆(或小顶堆),然后将堆顶元素(最大值或最小值)与堆尾元素互换,之后将剩余元素重新调整为大顶堆(或小顶堆),以此类推,直到整个数组有序。
堆的性质:小根堆:Ki <= K2i 且 Ki <= K2i+1
大根堆:Ki >= K2i 且 Ki >= K2i+1
代码实现(java):
public void sift(int low, int high){//筛选算法 int i = low, j = i * 2;//R[j]是R[i]的左孩子 RecType tmp = R[i];//tmp用于保存根节点 while(j <= high){ if(j < high && R[j].key < R[j+1].key) j ++;//若右孩子大,j指向右孩子 if(tmp.key < R[j].key)//tmp的孩子 较大,则R[j]调整到双亲位置 { R[i] = R[j]; i = j; j = 2 * i; } else break;//孩子小,筛选结束 } R[i] = tmp;//原根节点放入最终位置 } public void HeapSort()//R[0..n-1]递增排序 { for (int i = n / 2; i >= 0; i--)//建立初始堆 sift(i,n - 1); for (int i = n - 1; i >= 1; i--) { RecType tmp = R[i]; R[i] = R[0]; R[0] = tmp; sift(0,i - 1); } }
算法性能:
总比较次数:C <= 2n
+n
元素移动次数:M=n
+ O(n)
最坏、最好、平均时间复杂度:O(n
)
空间复杂度O(1)
一种不稳定算法
-
二路归并排序
归并排序的原理:
将两个或两个以上的有序表合并成一个新的有序表。具体来说,归并排序使用了分治策略,将待排序的数组分成若干个子数组,每个子数组都是有序的,然后再将这些有序的子数组合并成一个完整的、有序的数组。合并的过程中需要进行比较和交换操作,直到整个数组有序。
例如,9,8,7,6,5,4,3,2,1,0
代码实现(java):
public void Merge(int low, int mid, int high){ RecType[] R1 = new RecType[high - low + 1]; int i = low, j = mid + 1, k = 0; while(i <= mid && j <= high){ if(R[i].key <= R[j].key){ R1[k] = R[j]; i ++; k ++; } else { R1[k] = R[j]; j ++; k ++; } } while(i <= mid) { R1[k] = R[i]; i ++; k ++; } while(j <= high) { R1[k] = R[j]; j ++; k ++; } for (k = 0, i = low; i<=high ; k++,i++) R[i] = R1[k]; } public void MergePass(int len){ int i; for (i = 0; i + 2 * len - 1 < n; i = i + 2 * len) //归并len长的相邻子表 Merge(i, i + len - 1, i + 2 * len - 1); if(i + len < n) Merge(i, i + len - 1, n - 1);//归并余下的表 }
算法性能:
最好、最好、平均的时间复杂度都是O(n
)
空间复杂度O(n)
自顶向下和自底向上:
自顶向下:是在初始序列基础上先分解在合并。
自底向上:是在初始序列基础上直接进行合并
-
基数排序
基数排序的原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。具体来说,它从最低位开始,对每一位数字进行比较和交换,使得每一位数字都按照从小到大的顺序排列。然后按照同样的方法对下一位数字进行排序,直到最高位。
举例来说就是:
代码实现(java):
public class RadixSortClass {
LinkNode head = null;
public void CreateList(int[] a){
LinkNode s,t;
head = new LinkNode(a[0]);
t = head;
for(int i = 1; i < a.length; i ++){
s = new LinkNode(a[i]);
t.next = s;
t = s;
}
t.next = null;
}
private int geti(int key,int r,int i){//基数为r时,关键字key的第i位
int k = 0;
for(int j = 0; j <= i;j ++){
k = key % r;
key /= r;
}
return k;
}
public void RadixSort(int d, int r){//d是最大位数,r是进制数
LinkNode p,t = null;
LinkNode[] h = new LinkNode[r];//队头链表
LinkNode[] tail = new LinkNode[r];//队尾链表
for (int i = 0; i < d; i++) {//低位->高位的循环
for (int j = 0; j < r; j++) h[j] = tail[j] = null;
p = head;
while (p!=null){//分配过程
int k = geti(p.key,r,i);
if(h[k] == null){
h[k] = p;
tail[k] = p;
}
else {
tail[k] = p;
tail[k].next = p;
}
p = p.next;
}
head = null;//重新收集过程
for (int j = 0; j < r; j++)
if(h[j]!=null){
if(head == null) {
head = h[j];
t = tail[j];
}
else {
t.next = h[j];
t = tail[j];
}
}
t.next = null;
}
}
public void Disp(){
LinkNode p = head;
while(p!=null){
System.out.print(p.key + " ");
p = p.next;
}
}
}
class LinkNode{
int key;
String e;
LinkNode next;
public LinkNode(int key) {
this.key = key;
this.next = null;
}
}
算法性能:
时间复杂度O(d(r+n))
空间复杂度O(1)
稳定的排序算法
稳定排序算法可以简记为:鸡(基数)毛(冒泡)插(插入)龟(归并)壳(网上无意中看见的,挺有意思的)