内部排序
排序分为内部排序和外部排序,区别在于存储数据的位置。
内部排序分别有五类:插入、选择、交换、归并和分配排序。
排序过程有两个操作:一是比较关键字大小,二是记录的移动或改变。
(1)插入排序
基本思想:每次将待排序的记录按关键字的大小插入到前面已经排序好的适当位置,直到全部记录插入完成。
主要方法:直接插入排序和希尔插入排序。
//直接插入排序 int i, j; //因为比较防止数组越界 for(i = 2; i <= n; i++) { //判断是否小于有序区最小值 if(R[i].key < R[i-1].key) { R[0] = R[i];//保留当前位置值 //从有序区最大值依次比较以确定插入合适位置 for(j = i - 1; R[0].key < R[j].key; j--) R[j+1] = R[j]; //插入当前比较的值 R[j+1] = R[0]; } }
希尔排序基本思想:通过一个整数作为增量进行分组内进行直接插入排序,直到增量等于1时把所有元素在一组内排序。
void ShellInsert(SeqList R, int dk, int n) { int i, j; //从增量加一开始,等于多少个增量分组 for(i = dk+1; i <= n; i++) { //判断每个分组内是否需要交换位置 if (R[i].key < R[i-dk].key) { R[0] = R[i];//临时存储最小值 j = i - dk; while(j > 0 && R[0].key < R[j].key) { //交换同一个组内值 R[j+dk] = R[j]; j = j - dk; } R[j+dk] = R[0]; } } } void ShellSort(SeqList R, int d[], int t, int n) { int k; //d增量数组[5,3,1] for(k = 0; k < t; k++) ShellInsert(R, d[k], n); }
(2)选择排序
基本思想:每次将待排序记录关键词最小记录,依次放在排序好的记录最后,直到全部记录排序完成。
主要方法:直接选择排序和堆排序两种。
//直接选择排序 int i, j, k; for (i = 0; i < n; i++) { k = i;//插入初始值 for (j = i + 1; j <= n; j++) { if (R[j].key < R[k].key)//更新最小值位置 k = j; } //如果位置有变化表示更新值 if (k != i) { R[0] = R[i]; R[i] = R[k]; R[k] = R[0]; } }
堆排序是一种树型选择排序,基本思想是把数组当成一棵完全二叉树的顺序存储结构,堆的定义是n个记录的序列k1, k2, ..., kn称为堆,满足每个子树的根大于叶子结点为大根堆,小于为小根堆。
每一次建堆把堆中最顶端根的值排序到已经记录中,直到排序完成。
void Sift(SeqList R, int i, int h) { int j; RecType x = R[i];//从树的结点开始 j = 2 * i;//当前结点的子树 //只要满足树的高度 while (j <= h) { //判断是否有右结点 if (j < h && R[j].key < R[j+1].key) j++; //如果当前结点大于子树结点跳过 if (x.key >= R[j].key) break; //移动结点到 R[i] = R[j]; //继续找子树的子树 i = j; j = i * 2; } //保存初始结点的内容 R[i] = x; } void HeapSort(SeqList R, int n) { int i; //初始堆 for (i = n / 2; i > 0; i--) Sift(R, i, n); for (i = n; i > 1; i--) { //交换最后结点和根结点 R[0] = R[1]; R[1] = R[i]; R[i] = R[0]; Sift(R, i, i-1);//从剩余结点中获取最大值 } }
(3)交换排序
基本思想:两两比较待排序记录的关键字,发现两个记录的次序相反是进行交换,直到所有记录都没有反序为止。
主要方法:冒泡排序和快速排序。
冒泡排序基本思想:通过相邻元素之间的比较和交换,使关键字较小的元素逐渐从底部移向顶部。
int i, j , flag; for (i = 1; i < n; i++) { flag = 0;//判断是否有交换 //从最后元素开始直到第二个元素 for (j = n; j >= i+1; j--) { //判断当前元素小于前一个元素的值进行交换 if (R[j].key < R[j-1].key) { R[0] = R[j-1]; R[j-1] = R[j]; R[j] = R[0]; flag = 1; } } if(flag == 0) return; }
快速排序的基本思想:从所有元素中任选一个值作为基准,以它为分界拆成两个集合,然后分界左边位置的值小于基准值,右边的位置值大于基准值,这样称为一趟快速排序。具体操作设两个指针i和j,它们初始值为low和high,其基准值为x=R[i],首先从j所指向的位置向前搜索直到找到小于基准值得关键字存入当前i所指向的位置,i自增1,然后从i开始位置向后搜索大于基准值的关键字存入当前j的位置,j自减1,重复这个过程直到i等于j为止,完成一趟快速排序过程。
int Partition(SeqList R, int i, int j) { RecType x = R[i];//基准值 //满足i等于j一趟排序完成 while (i < j) { //从后往前寻找小于基准值的关键字 while (i < j && R[j].key >= x.key) j--; //如果不是同一个位置 if (i < j) { //交换当前位置关键字 R[i] = R[j]; i++; } //从前往后寻找大于基准值的关键字 while (i < j && R[i].key <= x.key) i++; if (i < j) { R[j] = R[i]; j--; } } //最后为基准值 R[i] = x; //以基准值下标作为后面排序关键点 return i; } /** * @brief * 快速排序 * @param R * @param low * @param high */ void QuickSort(SeqList R, int low, int high) { int p; if (low < high) { p = Partition(R, low, high);//划分中间 QuickSort(R, low, p-1);//左分区 QuickSort(R, p+1, high);//右分区 } }
(4)归并排序
基本思想:把待排序的文件n个长度的为1的有序子文件,这些文件两两归并,获得【n/2】个长度2的有序子文件,然后再重复进行,直到一个长度n的有序文件。
(5) 分配排序
箱排序基本思想:设置若干箱子,依次扫描待排序记录,把关键字等于k的记录全部装入第k个箱子里,然后按序号依次将各非空的箱子首尾连接起来。