数据结构专栏:
本章目录
折半查找的递归实现
/*
折半查找的递归实现
*/
#include <stdio.h>
#include<stdlib.h>
int halfSearch(int *arr, int num, int low, int mid, int high) {
if (low > high)return -1;
if (num == arr[mid]) {//找到,返回位置
return mid;
}
else if (num > arr[mid]) {
return halfSearch(arr, num, mid + 1, (mid + 1 + high) / 2, high);
}
else {
return halfSearch(arr, num, low, (low + mid - 1) / 2, mid - 1);
}
}
int main() {
int arr[] = { 0,1,3, 5,9,12,24,25,35,44,54,55 };
printf("%d ", halfSearch(arr, 100, 1, 6, 11));
return 0;
}
直接插入排序
/*
直接插入排序:
从第一个数开始,插入到它前面应该在的位置
*/
#include <stdio.h>
#include <stdlib.h>
void directInsertSort(int *arr,int len) {
for (int i = 0; i < len;i++) {//进行len次循环
for (int j = 0; j < i;j++) {
if (arr[i] < arr[j]) {//此时找到了它的归宿
int nowN = arr[i];//将需调换的值暂存
for (int k = i-1; k >= j;k--) {//从下标为j的元素开始往后移,腾位置
arr[k+1] = arr[k];
}
arr[j] = nowN;
}
}
}
}
int main() {
int arr[] = {5,3,4,10,8,9,7,12};
directInsertSort(arr,8);
for (int i = 0; i < 8;i++) {
printf("%d ",arr[i]);
}
return 0;
}
希尔排序
/*
希尔排序:
直接插入排序的改进版本,初始时会有一个排序增量,通常为数组长度的一半,按此增量进行直接插入排序,
每轮操作后将增量/2,
直至增量为一,此时相当于进行一次原始直接插入排序
*/
#include <stdio.h>
#include <stdlib.h>
void shellSort(int *arr, int len) {
int d = len / 2;
while (d >= 1) {
for (int i = 0; i < d; i++) {//对于d增量内的每一个元素直接插入排序
for (int j = i + d; j < len; j += d) {//按增量寻找是否存在逆序元素
if (arr[j] < arr[j - d]) {//找到了逆序元素
int numK = arr[j], k;//将该值暂存于numK
for (k = j; numK < arr[k - d]; k -= d) {//按d增量向后移元素,判断依据是前面的元素都大于找到的逆序元素
arr[k] = arr[k - d];
}
arr[k] = numK;//最后将逆序元素归位,而位置就是k
}
}
}
d = d / 2;
}
}
int main() {
int arr[] = { 9,3,4,10,2,5,7,12,10,15 };
shellSort(arr, 10);
for (int i = 0; i < 10; i++) {
printf("%d ", arr[i]);
}
return 0;
}
冒泡排序
/*
冒泡排序:
依次比较两相邻的元素值,逆序则交换
*/
#include <stdio.h>
#include <stdlib.h>
void bubbleSort(int *arr, int len) {
for (int i = 0; i < len - 1; i++) {//只需要len-1趟遍历
int flag = 0;//标志本轮是否有操作
for (int j = 0; j < len - 1; j++) {
int tmp;
if (arr[j] > arr[j + 1]) {//逆序则交换
tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
flag = 1;
}
}
if (!flag) {
break;
}
}
}
int main() {
int arr[10] = { 9,3,4,10,8,5,7,12,10,15 };
bubbleSort(arr, 10);
return 0;
}
双向冒泡排序
/*
双向冒泡排序:
所谓双向,即两个方向同时进行排序操作,可以在一定程度上降低时间开销。
从左向右大值往下沉,从右往左小值往上冒,只需要的遍历次数为元素个数的一半
*/
#include <stdio.h>
#include <stdlib.h>
void swap(int &a, int &b) {
int tmp;
tmp = a;
a = b;
b = tmp;
}
void BibubbleSort(int *arr, int len) {
int low = 0, high = len - 1;
int flag = 1;
while (low < high && flag) {//循环条件
flag = 0;//标志本轮是否有操作
for (int j = low; j < high; j++) {//往下沉
if (arr[j] > arr[j + 1]) {//逆序则交换
swap(arr[j], arr[j + 1]);
flag = 1;
}
}
high--;
for (int j = high; j > low; j--) {//往上冒
if (arr[j] < arr[j - 1]) {//逆序则交换
swap(arr[j], arr[j - 1]);
flag = 1;
}
}
low++;
}
}
int main() {
int arr[] = { 9,3,4,10,8,5,7,12,10,15 };
BibubbleSort(arr, 10);
return 0;
}
快速排序
/*
快速排序:
快速排序是一个典型的递归操作,这里我们描述一下一次快速排序的过程:我们会将数组的首元素作为枢纽元素,分设两个下标,low
为左边开始,high从末尾元素开始,先从high开始寻找第一个小于pivot的元素,紧接着从low开始寻找第一个大于pivot的元素,直至low==high
然后根据我们返回的pivotpos将数组划分为两个子表,进行递归操作
*/
#include <stdio.h>
#include <stdlib.h>
int partition(int *arr, int low, int high) {//该函数进行一次快速排序并返回基准元素最终所在位置
int pivot = arr[low];//枢纽元素
while (low < high) {
while (low < high&&arr[high] >= pivot)--high;//从high往下找第一个小于pivot的元素
arr[low] = arr[high];//移到low所在的位置
while (low < high&&arr[low] <= pivot)++low;//从low往上找第一个大于pivot的元素
arr[high] = arr[low];//移到high所在的位置
}
arr[low] = pivot;
return low;//返回存放枢纽的最终位置
}
void quickSort(int *arr,int low,int high) {
if (low<high) {//子表元素个数大于一个,进行快速排序
int pivotPos = partition(arr,low,high);//快速排序一次并获取存放枢纽的最终位置,用于划分子表
quickSort(arr,low,pivotPos-1);//左子表
quickSort(arr,pivotPos+1,high);//右子表
}
}
int main() {
int arr[] = { 5,3,4,10,6,11,12 };
quickSort(arr,0,6);
return 0;
}
归并排序
/*
归并排序:
*/
#include <stdio.h>
#include <stdlib.h>
static int *arrB = (int *)malloc(sizeof(int *)*20);
void merge(int *arr, int low, int mid, int high) {//一次归并排序
int k, m, n;
for (int i = low; i <= high; i++) {
arrB[i] = arr[i];
}
for (m = low, n = mid + 1, k = m; m <= mid && n <= high;) {
if (arrB[m] <= arrB[n]) {
arr[k++] = arrB[m++];
}
else {
arr[k++] = arrB[n++];
}
}
while (n <= high)arr[k++] = arrB[n++];
while (m <= mid)arr[k++] = arrB[m++];
}
void mergeSort(int *arr, int low, int high) {
if (low < high) {
int mid = (low + high) / 2;
mergeSort(arr, low, mid);
mergeSort(arr, mid + 1, high);
merge(arr, low, mid, high);
}
}
int main() {
int arr[] = { 38,49,65,97,76,13,27 };
mergeSort(arr, 0, 6);
return 0;
}
堆排序
/*
堆排序:我们可以将堆分为大根堆与小根堆其特性是根节点均大于左右孩子结点(大根堆),或根节点均小于左右孩子结点
这样我们便可以每次输出根节点,再把最后一个元素放到堆顶,进而重新构造一个堆
*/
#include <stdio.h>
void swap(int &a, int &b) {//交换函数
int tmp;
tmp = a;
a = b;
b = tmp;
}
void heapAdjust(int *arr,int k, int len) {//堆的调整,将传入的以k为根节点的子树调整为大根堆
arr[0] = arr[k];//暂存arr[k]
for (int i = 2 * k; i <= len;i*=2) {
if (i < len&&arr[i] < arr[i + 1])//如果右孩子更大,i指向右孩子
i++;
if (arr[0] >= arr[i]) break;
else {
arr[k] = arr[i];
k = i;//继续向下调整
}
}
arr[k] = arr[0];
}
void buildMaxHeap(int *arr,int len) {//初始建堆
for (int i = len / 2; i > 0;i--) {
heapAdjust(arr,i,len);
}
}
void heapSort(int *arr, int len) {
buildMaxHeap(arr,len);//建堆
for (int i = len; i >= 1;i--) {
printf("%d ",arr[1]);
swap(arr[i],arr[1]);//堆底弄上去
heapAdjust(arr,1,i-1);//把剩余的元素排成堆
}
}
int main() {
int arr[] = { 0,9,3,4,10 };
heapSort(arr,4);
return 0;
}
移动奇数到偶数前面
/*
设计一个算法:将顺序表中的奇数全部移到偶数前面(时间最少,空间最少)
分析:这里要求时空皆最少,所以不会使用到辅助空间,时间复杂度应为O(n)。其实我们可以一次遍历便可做到该要求
我们可以设计一个变量k=0,然后进行遍历,每当遇到一个奇数,便把奇数与k所对应的元素对调,k进行加一操作
书上采用的是快速排序,我没想到哈哈哈
*/
#include <stdio.h>
#include <stdlib.h>
void swap(int &a, int &b) {
int tmp;
tmp = a;
a = b;
b = tmp;
}
void oddAHead(int *arr, int len) {
int k = 0;
for (int i = 0; i < len; i++) {
if (arr[i] % 2) {
swap(arr[i], arr[k++]);
}
}
}
int main() {
int arr[] = { 1,2,4,4,5,6,7,8,9 };
oddAHead(arr,9);
return 0;
}
查找第k小的元素
/*
编写一个算法,使之能够在数组中找到第k小的元素
分析:我们可以采用先排序的方式,但这样时间复杂度偏高;这里我们采用快速排序来进行思考,我们知道快速排序每一趟会确定一个元素的最终
位置,假设为m,
若m=k,那么此时便找到了第k小的元素
若m<k,说明第k小的元素还在m的右半部分,继续查找第k小元素
若m>k,说明第k小的元素在m的左半部分,继续查找第k小元素
该算法的平均时间复杂度为O(n)
*/
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
//进行一次快速排序并返回枢纽位置
int partition(int *arr, int low, int high) {
int pivot = arr[low];
while (low < high) {
while (low < high && arr[high] >= pivot)--high;
arr[low] = arr[high];
while (low < high && arr[low] <= pivot)++low;
arr[high] = arr[low];
}
arr[low] = pivot;
return low;
}
//快速排序
int quickSort(int *arr, int low, int high, int k) {
if (low < high) {
int pivotPos = partition(arr, low, high);
if (pivotPos == k)return arr[pivotPos];
if (pivotPos < k)return quickSort(arr, pivotPos + 1, high, k);
if (pivotPos > k)return quickSort(arr, low, pivotPos - 1, k);
}
else if (low == high && low == k) {//区间内只有一个元素
return arr[k];
}
}
int main() {
int arr[] = { 0,1,5,6,3,4,7,11,10 };//1 3 4 5 6 7 10 11
int value;
int k;
printf("请输入要查找的数据:k=");
scanf("%d", &k);
getchar();
value = quickSort(arr, 1, 8, k);
printf("%d ", value);
return 0;
}
将数组划分大小两块-2016
/*
2016年408真题:集合A划分为A1、A2,个数分别为n1、n2,和分别为S1、S2,|n1-n2|最小|S1-S2|最大
分析:|n1-n2|最小,意思就是平分撒;|S1-S2|最大,意思就是把最大的那部分值和最小的那部分值分开
我们可以采用排序的方法,排好序,当然快速排序时间复杂度最低, 然后一分为二,十分的直接明了
但是我们可以利用快速排序的特性,做一些优化:当我们进行一次快速排序后
若pivotPos==n/2,则已将最小、最大的n/2元素分开
若pivotPos < n/2,则对pivotPos后的元素继续划分
若pivotPos > n/2,则对pivotPos前的元素继续划分
*/
#include <stdio.h>
//进行一次快速排序并返回枢纽位置
int partition(int *arr, int low, int high) {
int pivot = arr[low];
while (low < high) {
while (low < high && arr[high] >= pivot)--high;
arr[low] = arr[high];
while (low < high && arr[low] <= pivot)++low;
arr[high] = arr[low];
}
arr[low] = pivot;
return low;
}
//快速排序
void quickSort(int *arr, int low, int high, int mid) {
if (low < high) {
int pivotPos = partition(arr, low, high);
if (pivotPos == mid)return ;
if (pivotPos < mid) quickSort(arr, pivotPos + 1, high, mid);
if (pivotPos > mid) quickSort(arr, low, pivotPos - 1, mid);
}
}
int main() {
int arr[] = { 1,5,6,3,4,7,11,10 };
quickSort(arr, 0, 7, 3);
return 0;
}
荷兰国旗
/*
荷兰国旗问题:
这里要求时间复杂度为O(n),显然这是排序所无法完成的,所以我们需要另辟蹊径。
我们知道,快速排序的一次划分,可以以枢纽为界,左边均小于枢纽值,右边均大于枢纽值,
而这里恰好是三种颜色,我们用0代表红色,1代表白色,2代表蓝色,那么如果我们以1位枢纽,
进行一次快速排序的过程后,则可以达到要求,其因为我们仅仅遍历了一次数组,时间复杂度
为O(n)
*/
#include <stdio.h>
#include <stdlib.h>
void swap(int &a, int &b) {
int tmp;
tmp = a;
a = b;
b = tmp;
}
void subQuick(int *arr, int len) {
int left = 0, cur = 0, right = len-1;
while (cur <= right) {
if (arr[cur] == 2) {//等于2时放到最后,即与right交换
swap(arr[cur], arr[right--]);
}
else if (arr[cur] == 0) {//等于0时放到最前,即与left交换
swap(arr[cur++], arr[left++]);
}
else
cur++;//等于1时不交换,直接下一个
}
}
int main() {
int arr[] = { 0,2,1,2,1,2,1 };
subQuick(arr, 7);
return 0;
}
在单链表中进行简单选择排序
/*
简单排序在单链表中使用:
分析:
其实原理相同,只是单链表就失去了随机访问的优势,要注意防止断链
*/
#include <stdio.h>
#include <stdlib.h>
struct Link {
int data;
struct Link *next;
};
void simpleSort(Link *l) {//简单排序,每次选择一个最小的排在前面
Link *p, *pre, *m, *preM;
while (l->next) {
p = m = l->next, pre = preM = l;
while (p) {
if (p->data < m->data) {
preM = pre;
m = p;
}
pre = p;
p = p->next;
}
if (m == l->next) {
l = l->next;
}
else {
preM->next = m->next;
m->next = l->next;
l->next = m;
l = m;
}
}
}
int main() {
struct Link *l;
Link* createLink(int);
l = createLink(0);
simpleSort(l);
while (l->next) {
printf("%d ", l->next->data);
l = l->next;
}
return 0;
}
判断序列是否是小根堆
/*
编写算法判断一个序列是否是小根堆
分析:
小根堆的特性就是根节点小于左右孩子结点,对于一个序列我们可以将它看成完全二叉树,依次遍历,对每个节点进行判断
若有一个节点小于它的父亲节点则该序列不是小根堆,注意讨论单分支节点
*/
#include<stdio.h>
bool isMinHeap(int *arr, int len) {
if (len % 2 == 0) {//有一个单分支节点
if (arr[len ] < arr[len / 2 ]) {
return false;
}
for (int i = len / 2 -1; i > 0;i--) {
if (arr[i] > arr[2 * i] || arr[i] > arr[2 * i + 1])return false;
}
}
else {
for (int i = len / 2 ; i > 0; i--) {
if (arr[i] > arr[2 * i] || arr[i] > arr[2 * i + 1])return false;
}
}
return true;
}
int main() {
int arr[] = { 0,1,2,3,4,8,6,7};
if (isMinHeap(arr, 7)) {
printf("是小根堆");
}
else {
printf("不是小根堆");
}
return 0;
}
将序列中的Kn放入最终位置
/*
有一数组存放着无序序列K1,K2,K3,...,Kn,现要求将Kn放在将元素排序后的正确位置上,试编写实现该功能的算法
要求比较关键字的次数不超过n。
分析:
这里要求比较关键字的次数不超过n,就不能用复杂度为n²的排序算法,而且我们要用能够确定元素位置的排序算法,
所以我们首选快速排序
快速排序每一次可以确定一个元素的最终位置,这里我们既然要确定Kn的位置,那我们就把Kn最为枢纽元素,进行一次快速
排序, Kn的位置也就确定了
*/
#include <stdio.h>
void putKtoCurPos(int *arr, int low, int high) {
int pivot = arr[high];//将Kn作为枢纽
while (low < high) {
while (arr[low] <= pivot)++low;
arr[high] = arr[low];
while (arr[high] >= pivot)--high;
arr[low] = arr[high];
}
arr[low] = pivot;
}
int main() {
int arr[] = {2,5,4,1,3,11,5,8,4,6};
putKtoCurPos(arr,0,9);
for (int i = 0; i < 10;i++) {
printf("%d ",arr[i]);
}
return 0;
}
/*
现该功能的算法
要求比较关键字的次数不超过n。
分析:
这里要求比较关键字的次数不超过n,就不能用复杂度为n²的排序算法,而且我们要用能够确定元素位置的排序算法,
所以我们首选快速排序
快速排序每一次可以确定一个元素的最终位置,这里我们既然要确定Kn的位置,那我们就把Kn最为枢纽元素,进行一次快速
排序, Kn的位置也就确定了
*/
#include <stdio.h>
void putKtoCurPos(int *arr, int low, int high) {
int pivot = arr[high];//将Kn作为枢纽
while (low < high) {
while (arr[low] <= pivot)++low;
arr[high] = arr[low];
while (arr[high] >= pivot)--high;
arr[low] = arr[high];
}
arr[low] = pivot;
}
int main() {
int arr[] = {2,5,4,1,3,11,5,8,4,6};
putKtoCurPos(arr,0,9);
for (int i = 0; i < 10;i++) {
printf("%d ",arr[i]);
}
return 0;
}