资讯专栏INFORMATION COLUMN

《啊哈!算法》的笔记

Jonathan Shieber / 1019人阅读

摘要:本文作为啊哈算法里面的内容,作者整理了部分知识供大家参考,如有侵权联系删除。我们引入两个整型变量和,用来记录队列的队首第一位,用来记录队列的队尾最后一位的下一个位置。

本文作为《啊哈!算法》里面的内容,作者整理了部分知识供大家参考,如有侵权联系删除。

一大波数正在靠近——排序

最快最简单的排序——桶排序

简单介绍一下下图的主人公小哼

小哼的班上只有5个同学,这5个同学分别考了5分、3分、5分、2分和8分(满分为10分),按从大到小排序,排序后是 8 5 5 3 2。

我们只需申请一个大小为11的数组 int a[11],现在我们有编号从a[0]到a[10],我们将a[0]~a[10]初始为0,表示这些分数没人得到。如:a[0]=0表示目前没人0分。

根据小哼班里的成绩可得到最终结果如下图:

因此我们只需将出现过的分数打印处理就可以了。

简化版桶排序:有11个桶,编号从0~10,每出现一个数,就在对应编号的桶中放一个小旗子,最后数出每个桶中有几个小旗子。如下图:

代码实现:

#include int main() {	int a[11], i, j, t;	for(i = 0; i <= 10; i++) {		a[i] = 0; // 初始化为0	}	for(i = 1; i <= 5; i++) {		scanf("%d", &t); // 读入5个数到变量		a[t]++; // 计数	}	for(i = 10; i >=0; i--) {		for(j = 1; j <= a[i]; j++) {			printf("%d", i);		}	}	getchar();getchar(); // 暂停程序	return 0;}

该算法的时间复杂度是 O ( M + N ) O(M+N) O(M+N).

邻居好说话——冒泡排序

简化版的桶排序的缺点:浪费空间!

冒泡排序的基本思想:每次比较两个相邻的元素,如果它们顺序错误就把它们交换过来。

如 12 35 99 18 76 这5个数按从大到小排序,即越小的越靠后。首先比较第1位和第2位的大小,发现 12 比 35 小,交换这两个数的位置,交换后的顺序是 35 12 99 18 76,经过4次比较,得到 35 99 18 76 12。

我们刚刚将5个数中最小的一个归位了,每个将一个数归位我们称其位“一趟”。

总结:如果有n个数进行排序,只需将 n-1 个数归位(即n-1趟),每次都需从第1位开始进行比较,将较小的一个数放后面,比较完后向后挪一位继续比较下面两个相邻数的大小。

代码实现

#include int main() {	int a[100], i, j, t, n;	scanf("%d", &a[i]);	for (i = 1; i <= n; i++) {		scanf("%d", &a[i]);	}	for (i = 1; i <= n - i; j++) {		for (j = 1; j <= n - i; j++) {			if (a[j] < a[j + 1]) {				t = a[j];				a[j] = a[j + 1];				a[j + 1] = t;			}		}	}	for (i = 1; i <= n; i++) {		printf("%d", a[i]);	}	getchar(); getchar();	return 0;}

冒泡排序核心是双重嵌套循环,时间复杂度是 O ( N 2 ) O(N^2) O(N2)

最常用的排序——快速排序

我们对 6 1 2 7 9 3 4 5 10 8 这10个数进行排序,首先在这个序列中找一个数作基准数(用来做参照的数)。我们让6作基准数,将这个序列中所以大于基准数放6的右边,小于基准数的放6的左边。

方法:先从右往左找一个小于6的数,再从左往右找一个大于6的数然后交换。所以我们使用两个变量i 和j,分别指向序列最左边和最右边。我们为这两个变量取个名字“哨兵i"和”哨兵j“,刚开始让哨兵i指向序列最左边(i=1),指向数字6,让哨兵j指向序列最右边(j=10),指向数字8.

首先哨兵j开始出动,哨兵j向左移动(j–)直到找到一个小于6的数才停下来,接下来哨兵i向右移动(i++),直到找到一个大于6的数才停下来,最后哨兵j停在数字5,哨兵i停在数字7。

交换哨兵i和哨兵j所指向的元素的值

第一次交换结束,哨兵j继续向左移动,到数字4停下,哨兵i继续向右移动,到9停下,再次进行交换

第二次交换结束,哨兵j继续向左移动,到3停下哨兵i继续向右移动,哨兵i走到数字3与哨兵j相遇了

此时探测结束,我们将基准数6和3进行交换

整个算法的处理过程图

快速排序相比于冒泡排序,每次交换是跳跃式的,基于二分的思想。每次排序的时候设置一个基准点,将小于等于基准点的数放基准点左边,将大于等于基准点的数放基准点右边。

最坏的情况是相邻的两数进行交换,此时快速排序的时间复杂度是 O ( N 2 ) O(N^2) O(N2)(最差时间复杂度),平均时间复杂度是 O ( N l o g N ) O(Nlog_N) O(NlogN)

#include int a[101], n;void quicksort(int left, int right) {	int i, j, t, temp;	if (left > right) {		return;	}	temp = a[left];	i = left;	j = right;	while (i != j) {		while (a[j] >= temp && i < j) {			j--;		}		while (a[i] <= temp && i < j) {			i++;		}		if (i < j) {			t = a[i];			a[i] = a[j];			a[j] = t;		}		a[left] = a[i];		a[i] = temp;		quicksort(left, i - 1);		quicksort(i + 1, right);	}}int main() {	int i, j, t;	scanf("%d", &n);	for (i = 1; i <= n; i++) {		scanf("%d", &a[i]);	}	quicksort(1, n);	for (i = 1; i <= n; i++) {		printf("%d", a[i]);	}	getchar(); getchar();	return 0;}

小哼买书

小哼的学校要建一个图书角,老师派小哼去找同学做调查,每本书有唯一的ISBN号,小哼需要完成去重和排序的工作。

输入2行,第1行表示有多少个同学参加调查(1 ~ 100),第2行表示图书的ISBN号(1 ~ 1000)。

输出2行,第1行表示需要买多少书,第2行表示从小到大已排序好的图书的ISBN号。

方法一:去重后排序

#include int main() {	int a[1001], n, i, t;	for (i = 1; i <= 1000; i++) {		a[i] = 0; // 初始化	}	scanf("%d", &n); // 读入n	for (i = 1; i <= n; i++) {		scanf("%d", &t); 		a[t] = 1; // 标记	}	for (i = 1; i <= 1000; i++) {		if (a[i] == 1) {			printf("%d", i);		}	}	getchar(); getchar();	return 0;}

桶排序的时间复杂度: O ( N + M ) O(N+M) O(N+M)

方法二:排序后去重

#include int main() {	int a[1001], n, i, t;	for (i = 1; i <= n; i++) {		scanf("%d", &a[i]);	}	for (i = 1; i <= n - 1; i++) {		for (j = 1; j <= n - i; j++) {			if (a[j] > a[j + 1]) {				t = a[j];				a[j] = a[j + 1];				a[j + 1] = t;			}		}	}	printf("%d", a[i]);	for (i = 2; i <= n; i++) {		if (a[i] != a[i - 1]) {			printf("%d", a[i]);		}	}	getchar(); getchar();	return 0;}

时间复杂度: O ( N 2 ) O(N^2) O(N2)

栈、队列、链表

解密QQ号——队列

小哼向新同桌小哈询问QQ号,小哈给小哼一串加密过的数字6 3 1 7 5 8 9 2 4,解密规则是:首先将第1个数删除,接着将第2个数放到这串数的末尾,直到剩下最后一个数,将最后一个数也删除,按刚才删除的顺序,把这些删除的数连在一起就是小哈的QQ号了。

如何在数组中删除一个数?

最简单的方法:将后面的数都往前移动一位。

我们引入两个整型变量head和tail,head用来记录队列的队首(第一位),tail用来记录队列的队尾(最后一位)的下一个位置。我们这里规定队首和队尾重合时,队列为空。
我们将这9个数全部放入队列,head=1;tail=10;在队首删除一个数的操作是head++

在队尾增加一个数x的操作是q[tail]=x;tail++

整个解密过程图

#include int main() {	int q[102] = { 0, 6, 3, 1, 7, 5, 8, 9, 2,4 }, head, tail;	int i;	head = 1;	tail = 10;	while (head < tail) { // 队列不为空		printf("%d", q[head]); // 打印队首并出队		head++;		q[tail] = q[head]; // 将队首贴加到队尾		tail++;		head++; // 再将队首出队	}	getchar(); getchar();	return 0;}

队列:一种特殊的线性结构,只允许在队列的首部(head)进行删除操作,称为出队,队列的尾部(tail)进行插入操作,称为入队,当队列中没有元素是(head=tail)队列为空队列。

队列是我们今后学习广度优先搜索以及队列优化的Bellman-Ford 最短路算法的核心数据结构,我们将队列的三个基本元素(一个数组,两个变量)封装为一个结构体类型。

struct queue {	int data[100]; // 队列的主体	int head; // 队首	int tail; // 队尾};

定义结构体变量:

struct queue q;

访问结构体变量的内部成员

q.head = 1;q.tail = 1;scanf("%d", &q.data[q.tail]);

结构体实现的队列操作

#include struct queue {	int data[100]; // 队列的主体	int head; // 队首	int tail; // 队尾};int main() {	struct queue q;	int i;	q.head = 1;	q.tail = 1;	for (i = 1; i <= 9; i++) {		scanf("%d", &q.data[q.tail]);	}	while (head < tail) { // 队列不为空		printf("%d", q[head]); // 打印队首并出队		head++;		q[tail] = q[head]; // 将队首贴加到队尾		tail++;		head++; // 再将队首出队	}	getchar(); getchar();	return 0;	}

解密回文——栈

后进后出的数据结构——栈

栈限定为只能在一端进行插入和删除操作,如:有一个小桶直径只能放一个小球,我们依次放入2、1、3号小球,如果你想要拿出2号小球,必须先将3号小球拿出,再拿出1号小球,最后才能将2号小球拿出。

栈的实现:一个一维数组和一个指向栈顶的变量top,我们通过top来对栈进行插入和删除操作。
如:xyzyx 是一个回文字符串,所谓回文字符是正读反读均相同的字符序列。

读取这行字符串,求出这个字符串的长度

char a[101];int len;gets(a);len = strlen(a);

如果一个字符串是回文字符,那么它必须是中间对称的,所以我们求中点

mid = len / 2 - 1;

我们先将mid之前的字符全部入栈,char s[101]; ,初始化栈 top=0;。入栈的操作是top++;s[top]=x;(入栈的字符暂存字符变量x中),可简写s[++top]=x;。

for(i = 0; i <= mid; i++) {	s[++top] = x;}

代码实现

#include #include int main() {	char a[101], s[101];	int i, len, mid, next, top;	gets(a); // 读入一行字符	len = strlen(a); // 求字符串长度	mid = len / 2 - 1; // 求字符串中点	top = 0; // 栈的初始化	for (i = 0; i <= mid; i++) {		s[++top] = a[i];	}	if (len % 2 == 0) { // 判断字符串长度是奇数还是偶数		next = mid + 1;	}	else {		next = mid + 2;	}	for (i = next; i <= len - 1; i++) {		if (a[i] != s[top]) {			break;		}		top--;	}	if (top == 0) { // top为0,表示栈内所以字符都被一一匹配		printf("YES");	}	else {		printf("NO");	}	getchar(); getchar();	return 0;}

链表

如果需要在8前面插入一个6,无需像数组一样将8及后面的数都依次往后移一位,只需像下图一样

C语言中可以使用指针和动态分配内存函数 malloc来实现。

int *p; // 定义一个整型指针变量

指针作用:存储一个地址,存储一个内存空间的地址

p = &a; // &取地址符,整型指针p指向了整型变量a

通过指针p来输出变量a的值

#include int main() {	int a = 10;	int 
            
                     
             
               

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/120800.html

相关文章

  • JavaScript 之 面向对象 [ Object类型 ]

    摘要:类型描述类型对象是所有其他类型对象的父级使用创建对象表示使用构造函数方式创建一个空对象显示可以根据一个给定值创建一个对应类型的对象创建一个类型的对象显示啊哈哈创建一个类型的对象显示啊哈哈属性描述符表示对象的属性或方法的内部数据结构用于描述对 Object类型 描述 object类型对象是所有其他类型对象的父级 使用Object创建对象 /* 表示使用构造函数方式创建一个空对象 */ v...

    reclay 评论0 收藏0
  • JavaScript 之 核心语法 [ 数组 ]

    摘要:数组描述表示可以储存一个或多个数据值的有序集合数组中储存的数据中可以称为元素数组中可以储存任何类型的数据语法字面量方式数组名称元素,元素,构造函数方式数组名称元素元素函数方式数组名称元素元素长度表示数组的长度数组中储存元素的个数当使用 数组 描述 表示可以储存一个或多个数据值的有序集合 数组中储存的数据中可以称为元素 数组中可以储存任何类型的数据 语法 字面量方式 - var 数...

    mumumu 评论0 收藏0
  • 正则表达式之初入江湖

    摘要:拿举例子只想说明你总会在一些阴暗的角落遇到正则表达式,为了到时候不至于一头雾水,我们最好简单的了解一下正则表达式的使用。 为什么要学正则表达式 很多人对正则表达式的认知只是在进行表单验证的时候在网上搜一段正则表达式进行copy,实际工作上好像很难遇到大段的正则表达式 我第一次看到大量的正则使用是在jQuery源码中,当时看的头疼只好草草的看下大概思路不了了之,但是到今天我依然不认为这种...

    caige 评论0 收藏0
  • JavaScript 之 核心语法 [ 类型转换 ]

    摘要:隐式数据类型转换转换成类型字符串用法空字符串变量名称啊哈哈转换成字符串数字类型和布尔类型转换会将其值用字符串的方式显示数字类型转换成字符串类型显示显示转换后的类型布尔类型转换成字符串类型显示显示转换后的类型转换成类型数字用法加号变 隐式数据类型转换 转换成string类型(字符串) - 用法( - 空字符串) + 变量名称 var num = 100; var str1 = 啊哈哈; ...

    muzhuyu 评论0 收藏0
  • JavaScript 之 核心语法 [ 函数和作用域 ]

    摘要:函数描述表示定义一段代码,并且可重复使用函数的定义函数声明方式字面量表达方式函数声明方式语法函数名称小括号不可省去函数体函数声明方式在定义函数时,函数体中的内容不会被执行哑吼吼调用函数使用函数名称调用函数的时候会执行函数体中的内容字面量方 函数 描述 表示定义一段JavaScript代码,并且可重复使用 函数的定义 函数声明方式 字面量表达方式 函数声明方式 语法 function...

    joy968 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<