一、交换排序
以下排序均使用顺序表作为排序数据的存储结构
//顺序表基本运算算法 seqlist.h
#include <iostream>
#define MAXL 100 //最大长度
typedef int KeyType; //定义关键字类型为int
typedef char InfoType;
typedef struct
{
KeyType key; //关键字项
InfoType data; //其他数据项,类型为InfoType
} RecType; //查找元素的类型
void swap(RecType& x, RecType& y) //x和y交换
{
RecType tmp = x;
x = y;
y = tmp;
}
void CreateList(RecType R[], KeyType keys[], int n) //创建顺序表
{
for (int i = 0; i < n; i++) //R[0..n-1]存放排序记录
R[i].key = keys[i];
}
void DispList(RecType R[], int n) //输出顺序表
{
for (int i = 0; i < n; i++)
std::cout << R[i].key << " ";
std::cout << std::endl;
}
1、冒泡排序
一种典型的交换排序方法,其原理是两两比较排序数据元素的大小,发现两个数据元素的次序相反时,即进行交换,直到没有反序的数据为止。设想被排序的数组Array[0....N],垂直竖立,将每个数据元素看做有重量的气泡,根据轻气泡不能再重气泡之下的原则,从下往上扫描,反复进行。
注意:冒泡排序每趟产生的有序区一定是全局有序区,也就是说每趟产生的有序区中的元素都归位了。
#include <iostream>
#include "seqlist.h"
void BubbleSort(RecType R[], int n)
{
for (int i = 0; i < n-1; i++)
{
for (int j = n - 1; j > i; j--)
{
if (R[j].key < R[j - 1].key)
{
swap(R[j], R[j - 1]);
}
}
}
}
int main()
{
KeyType a[] = { 5,7,3,2,8,6,1,9 };
RecType R[MAXL];
CreateList(R,a,8);
BubbleSort(R,8);
DispList(R,8);
return 0;
}
在排序时经常会发现,在第i趟已经排好了,但是仍然指定后边几趟的比较。实际上一旦在某一趟比较中没有出现元素交换,则说明已经排好了,后边的比较就变成了多余的了。下边是改进版的冒泡排序。仅仅是加入一个bool标志来标识某一趟有没有发生交换,如果发生就结束循环。
void BubbleSort1(RecType R[], int n)
{
bool exchange = false;
for (int i = 0; i < n - 1; i++)
{
for (int j = n - 1; j > i; j--)
{
if (R[j].key < R[j - 1].key)
{
swap(R[j], R[j - 1]);
exchange = true; //有交换,说明还没排好
}
}
if (!exchange)
return;
}
}
算法分析:最好情况(初始序列已经是正序)时间负责度为O(n)、最坏情况为O(n²),平均时间复杂度为O(n²),接近最坏情况,在时间性能上比直接插入排序差。
空间复杂度O(1)。最后冒泡排序是一种稳定排序方法。
2、快速排序
快排由冒泡排序改进而来。
基本思想是在待排序的n个元素中任取一个元素,(通常去第一个元素为基准),把该元素放进适当位置后,数据序列此时被基准分成两部分,左边的元素都比基准小,右边的元素都比基准大。一趟划分过程采用从两头向中间扫描的办法,同时交换与基准元素逆序的元素。
之后对产生的两个部分分别重复以上一趟划分,直至每部分内只有一个元素或没有为止。快速排序每趟仅将一个元素归位。
#include "seqlist.h"
int partition(RecType R[], int s, int t) //一趟划分
{
int i = s, j = t;
RecType tmp = R[i]; //以R[i]为基准
while (i < j) //从两端交替向中间扫描,直至i=j
{
while (i < j && R[j].key >= tmp.key)
j--; //从右向左扫描,找到一个小于tmp.key的R[j],放到R[i]处
R[i] = R[j];
while (i < j && R[i].key <= tmp.key)
i++; //从左向右扫描,找到一个大于tmp.key的R[i],放到R[j]处
R[j] = R[i];
}
R[i] = tmp;
return i;
}
void QuickSort(RecType R[], int s, int t) //对R[s...t]排序
{
int i = 0;
if (s < t)
{
i = partition(R, s, t); //一次排序
QuickSort(R, s, i - 1); //对左区间递归排序
QuickSort(R, i + 1, t); //对右区间递归排序
}
}
int main()
{
KeyType a[] = { 5,7,3,2,8,6,1,9 };
RecType R[MAXL];
CreateList(R,a,8);
QuickSort(R,0,7);
DispList(R,8);
return 0;
}
int once_quick_sort(vector<int>& v, int left, int right)
{
int key = v[left]; //用第一个元素作为中轴元素,
while (left < right)
{
while (left < right && key <= v[right])
{ // 然后从右往左找到一个小于key的元素,若没找到,一直左移,
right--;
}
if (left < right) //若找到,完成替换
{
v[left++] = v[right];
}
while (left<right && key>v[left])
{ // 然后从左往右找到一个大于key的元素,若没找到,一直右移,
left++;
}
if (left < right) //若找到,完成替换
{
v[right--] = v[left];
}
v[left] = key; //完成一次排序,left与right相遇,将key 赋值给left,并返回
return left;
}
}
void quick_sort(vector<int>& v, int left, int right)
{
if (left < right)
return -1;
int middle = once_quick_sort(v, left, right);
quick_sort(v, left, middle - 1);
quick_sort(v,middle + 1, right);
}
算法分析:先不写了。对数打不出来
二、插入排序
插入排序的思想是:每次将一个待排序的元素按其关键字大小插入到前面已经排好序的子集中的适当位置,知道全部元素插入完成为止。
1、直接插入排序
直接插入排序的一次操作是将当前无序区的一个元素插入到有序区的适当位置,结果是有序区元素加一,这种方法称为增量法,因为它每次使有序区增加一个元素。
每趟产生的有序区不一定是全局有序区。
将无序区一个元素R[i]插入到有序区的过程:将R[i]暂存到tmp,j在有序区从后往前比较,凡是比tmp.key大的元素均后移一个位置,若找到某个关键字<=tmp.key,则将tmp插入到它后边
#include "seqlist.h"
void InsertSort(RecType R[], int n)
{
for (int i = 1; i < n; i++) //初始时有序区只有R[0]一个元素
{
if (R[i].key < R[i - 1].key)
{
RecType tmp = R[i];
int j = i - 1; // j 是有序区末尾下标
do //找R[i]的插入位置
{
R[j + 1] = R[j];
j--;
} while (j >= 0 && R[j].key > tmp.key); //有序区的元素大于将要插入的元素
R[j + 1] = tmp; //+1的原因在于 先do{}
}
}
}
int main()
{
KeyType a[] = { 5,7,3,2,8,6,1,9 };
RecType R[MAXL];
CreateList(R,a,8);
InsertSort(R,8);
DispList(R,8);
return 0;
}
算法分析:最好情况(初始序列已经是正序)时间负责度为O(n)、最坏情况为O(n²),平均时间复杂度为O(n²),接近最坏情况。
空间复杂度O(1)。直接插入排序也是一种稳定排序方法。
2、折半插入排序
在直接插入的基础上,由于有序区元素是有序的,因此可以使用折半查找的方法先找到插入位置,再通过移动元素进行插入。
每趟产生的有序区不一定是全局有序区。
void BinInsertSort(RecType R[], int n)
{
for (int i = 1; i < n; i++)
{
if (R[i].key < R[i - 1].key)
{
RecType temp = R[i];
int low = 0;
int high = i - 1;
while(low<=high) //在R[low......high]中查找插入位置
{
int mid = (low + high) / 2;
if (temp.key < R[mid].key)
high = mid - 1; //插入点在左半区
else
low = mid + 1; //插入点在右半区
}
for (int j = i - 1; j < high + 1; j--) //元素后移
R[j + 1] = R[j];
R[high + 1] = temp; //插入temp
}
}
}
算法分析:平均时间复杂度为O(n²) ,一种稳定的排序方法。比起直接插入排序在性能上并没有很大改善。
3、希尔排序(shell sort)
#include "seqlist.cpp"
void ShellSort(RecType R[],int n) //希尔排序算法
{ int i,j,d;
RecType tmp;
d=n/2; //增量置初值
while (d>0)
{ for (i=d;i<n;i++) //对所有组采用直接插入排序
{ tmp=R[i]; //对相隔d个位置一组采用直接插入排序
j=i-d;
while (j>=0 && tmp.key<R[j].key)
{ R[j+d]=R[j];
j=j-d;
}
R[j+d]=tmp;
}
printf(" d=%d: ",d); DispList(R,n);
d=d/2; //减小增量
}
}
算法分析:
三、选择排序
1、普通插入排序
将序列分为有序区和无序区,每一趟,从无序区中找到最小(或者最大)的元素,放到有序区序列的最后一个位置。(有序区最后一个位置即无序区第一个位置)
//普通选择排序 不稳定 最差O(n^2) 最好O(n^2) 平均O(n^2)
void select_sort(int* arr, int size)
{
if (arr == NULL)
return;
int i, j, k;
//刚开始不存在有序区,i是无序区第一个元素的下标
for (i = 0; i < size; i++)
{
k = i;
for (j = i + 1; j < size; j++)
{
if (arr[k] > arr[j]) // 使用k来标记无序区中最小元素下标
{
cout <<"j: "<< arr[j] << endl;
k = j;
}
}
swap(arr[i], arr[k]); //用无序区中最小元素交换无序区中第一个元素
for (int q = 0; q < 8; q++)
{
cout << arr[q] << " ";
}
cout << endl;
}
}
void select_sort_pro(int* arr, int size)
{// 每趟排序同时找出最大值和最小值,把最小值放在序列左边
//最大的放在无序区右边,left和right是无序区的左右边界,
//用于定位无序区的最小与最大位置,待存入相应数据
if (arr == NULL)
return;
int left = 0;
int right = size - 1;
while (left < right)
{
for (int i = left+1; i < right; i++)
{
if (arr[i] < arr[left])
{
swap(arr[i], arr[left]);
}
if (arr[i] > arr[right])
{
swap(arr[i], arr[right]);
}
}
left++;
right--;
}
}
----------------今天先写到这------------------