《啊哈!算法》学习笔记
如果需要相关书籍可以私信我或者在下面评论.
第1章 排序
第1节 最快最简单的排序——桶排序
算法思路:数据范围在n-m中的数据,开辟数组下标包括n-m的数组,进行输入,遇到一次计数一次,然后对数组进行输出。
#include<bits/stdc++.h>
using namespace std;
int nums[5]={0};
int main(){
for(int i=0;i<4;++i){
int temp;
cin>>temp;
nums[temp]++;
}
for(int i=0;i<4;++i){
for(int j=1;j<=nums[i];++i){
cout<<i<<" ";
}
}
return 0;
}
时间复杂度:桶的个数m,输入个数n,O(M+2*N)
第2节 冒泡排序
算法思路:最后我们总结一下:如果有 n 个数进行排序,只需将 n1 个数归位,也就是说要进行n-1 趟操作。而“每一趟”都需要从第 1 位开始进行相邻两个数的比较,将较小的一个数放在后面,比较完毕后向后挪一位继续比较下面两个相邻数的大小,重复此骤,直到最后一个尚未归位的数,已经归位的数则无需再进行比较
cout << "请输入数组大小" << endl;
int N;
cin >> N;
int* p = new int[N + 1];
for (int i = 1; i <=N; ++i) {
cin >> p[i];
}
for (int i = 1; i <= N - 1; ++i) {//趟数,总共为N-1
for (int j = 1; j <= N - i; ++j) {//从第一个数开始比较,比较到该趟的倒数第二个数
if (p[j] > p[j + 1]) {//从小到大排序
int temp = p[j];
p[j] = p[j + 1];
p[j + 1] = temp;
}
}
}
for (int i = 1; i <= N; ++i) {
cout << p[i] << " ";
}
delete[] p;
时间复杂度:O(2N+NN)即O(N*N)
第3节 快速排序
算法思路:每次设置一个基准数,设置一个左右哨兵,进行搜寻,哨兵的位置即为数组的下标。左哨兵寻找比基准数大的数,右哨兵寻找比基准数小的数(从小到大排序)。然后交换数据,直到左右哨兵相遇,交换基准数,继续处理子序列。直到序列之中只剩一个数时。
void quickSort(int left ,int right,int *p) {//从小到大排序
//每次序列的第一个数即left做对应的数即为基准数
int i, j, t, temp;//i左哨兵,j右哨兵。
if (left > right) return;//不符合条件退出
temp = p[left];//temp中存储基准数
i = left;
j = right;
while (i != j) {//说明这趟数据处理完毕,
//必须j先走。因为如果反之,i在大于基准数的地方停下,j在小于基准数的地方停下,如果i先走,最后停下跟基准数交换时,总是大于基准数的。
while (p[j] >= temp && i < j) {//当右边找到比基数小的数时,或者没找到时,退出循环
j--;
}
while (p[i] <= temp && i < j) {//当左边找到比基数大的数时,或者没找到时,退出循环
i++;
}
if (i < j) {//说明正常退出循环,找到了符合的数,进行交换数据。
t = p[j];
p[j] = p[i];
p[i] = t;
}
}
//数据处理完毕以后,最终将基准数归位,i也可以替换为j。因为此时i==j。
p[left] = p[j];//将满足的数据换到数列的第一个位置
p[i] = temp;//将基准数换到数列中
quickSort(left, i - 1,p);//处理子左数列
quickSort(j + 1, right,p);//处理子右数列
}
int main()
{
#pragma region 快速排序
cout << "请输入数组大小" << endl;
int N; cin >> N;
int* p = new int[N + 1];
for (int i = 1; i <= N; ++i) cin >> p[i];
quickSort(1, N,p);
for (int i = 1; i <= N; ++i) {
cout << p[i] << " ";
}
delete[]p;
时间复杂度:快速排序的最差时间复杂度和冒泡排序是一样的,都是 O(N*N),它的平均时间复杂度为 O (NlogN)
第2章 栈、队列、链表
第1节 解密QQ号—队列
问题描述:首先将第 1 个数删除,紧接着将第 2 个数放到这串数的末尾,再将第 3 个数删除并将第 4 个数放到这串数的末尾,再将第 5 个数删除……直到剩下最后一个数,将最后一个数也删除。按照刚才删除的顺序,把这些删除的数连在一起就是小哈的 QQ 啦。
int count = 0,head=1,tail=0;//count用于记录QQ号码长度,head头指针,tail尾指针
cout << "请输入QQ号码长度" << endl;
cin >> count;
for (int i = 1; i <= count; ++i) cin >> nums_03[i];
tail = count + 1;//对尾指针进行初始化
while (head < tail) {//当队列不为空持续循环
cout << nums_03[head];
head++;//指向要被添加到末尾的数字
nums_03[tail] = nums_03[head];
head++;//指向下一个要被删除的数
tail++;
}
做题感悟:实现覆盖删除有两个方法
- 可以将后面的数依次前移,不过太费时间
- 可以建立一个表示物,将表示物有指向被删除的数组元素指向别的地方,即实现了删除。
引出概念队列:
是一种特殊的线性结构,只能在队首进行出队操作,在队尾进行入队操作。有一个头指针和尾指针(一般尾指针指向最后一个元素的后一个位置比较好),当队列中元素为空时,head==tail。
类型定义:
struct queue{
ElementType data[//数组大小count];//数据域
int head;//头指针
int tail;//尾指针
};//此处分号不要丢
//定义该类型变量时要带struct
struct queue a;
//当在结构体前面加typedef
typedef struct queue{};
queue a;//OK
第2节 解密回文–栈
-
问题描述:我们来看一个例子。“xyzyx”是一个回文字符串,所谓回文字符串就是指正读反读均相同的字符序列,如“席主席”、“记书记”、“aha”和“ahaha”均是回文,但“ahah”不是回文。通过栈这个数据结构我们将很容易判断一个字符串是否为回文。
-
算法思路:进行前后字符系列的判断,可以利用栈的结构,将前部分压入栈,正好与右边子序列字符顺序一样。
判断回文数列
#include <iostream>
#include <cstring>
int main()
{
char a[101], s[101];
int i, len, mid, next, top;
gets_s(a); //读入一行字符串
len = strlen(a); //求字符串的长度
mid = len / 2 - 1; //求字符串的中点。当len为奇数时,len/2为中点元素的下标。当len为偶数时,mid需要为一半的最后一个
//mid需要为中点字符的前一个,所以均将减一
top = 0;//栈的初始化
//将mid前的字符依次入栈
for (i = 0; i <= mid; i++)
s[++top] = a[i];//从下标为1处开始计入字符
//判断字符串的长度是奇数还是偶数,并找出需要进行字符匹配的起始下标
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--;
}
//如果top的值为0,则说明栈内所有的字符都被一一匹配了
if (top == 0)
printf("YES");
else
printf("NO");
return 0;
}
第3节 纸牌游戏—小猫钓鱼
-
问题描述:星期天小哼和小哈约在一起玩桌游,他们正在玩一个非常古怪的扑克游戏——“小猫钓鱼”。游戏的规则是这样的:将一副扑克牌平均分成两份,每人拿一份。小哼先拿出手中的第一张扑克牌放在桌上,然后小哈也拿出手中的第一张扑克牌,并放在小哼刚打出的扑克牌的上面,就像这样两人交替出牌。出牌时,如果某人打出的牌与桌上某张牌的牌面相同,即可将两张相同的牌及其中间所夹的牌全部取走,并依次放到自己手中牌的末尾。当任意一人手中的牌全部出完时,游戏结束,对手获胜。这里我们做一个约定,小哼和小哈手中牌的牌面只有 1~9。
-
算法思路:需要用到的数据结构:栈和队列。桌子上的牌面即为栈,当打入一张牌时即为入栈。小哼和小哈手中即为队列,出牌为出队,赢牌为入队。
-
解题步骤:
先输入小哼和小哈手中的牌。
然后进行打牌操作,对队列的首尾指针进行相应操作,判断是否和桌子上的牌相同。相同怎样,不相同怎样
然后对是否赢牌进行判断操作。
#include <iostream>
#include <cstring>
#define ElementType int
using namespace std;
int flag = 0, temp;//flag用于标识出的牌是否和桌面上的牌重复,temp存储出的牌
typedef struct queue {//队列的结构定义
ElementType data[101];
int head;//头指针
int tail;//尾指针,队尾指针对应的元素实际上没有值,它的前一位对应的元素才有值。想清楚队尾指针的位置
};
typedef struct stack {//栈的结构定义
ElementType data[10];//因为桌面上只有1-9
int top;//栈顶指针,从下标为1开始存储
};
void putCard(queue& q1,stack& s) {
flag = 0;//新的一轮出牌需要重新初始标记
temp = q1.data[q1.head];
for (int i = 1; i <= s.top; ++i) {//判断是否和桌面上的牌重复
if (temp == s.data[i]) {
flag++; break;//flag值改变说明出的牌对应上桌面上某张牌
}
}
if (flag == 0) {//出的牌没有与桌面上的牌重复
q1.head++;//队首指针加1,表示下一张牌
s.top++;//栈顶指针加1
s.data[s.top] = temp;
}
if (flag != 0) {//说明重复,赢牌了
q1.head++;//队首指针加1,表示下一张牌
q1.data[q1.tail] = temp;//将打出去的牌回收
q1.tail++;
//下面将赢得牌收入手中
while (temp != s.data[s.top]) {
q1.data[q1.tail] = s.data[s.top];//存储牌
q1.tail++;//队尾指针加1
s.top--;//栈顶指针减1
}
}
}
void winGame(queue q1, queue q2, stack s) {
if (q1.head == q1.tail) {//说明小哼手中牌空了,没有赢,反之小哈赢了
cout << "小哈赢了\n";
cout << "小哈手中的牌为:";
for (int i = q2.head; i < q2.tail; ++i) {//输出小哈手中的牌
cout << q2.data[i] << " ";
}
cout << endl;
}
if (q2.head == q2.tail) {//说明小哈手中牌空了,没有赢,反之小哼赢了
cout << "小哼赢了\n";
cout << "小哼手中的牌为:";
for (int i = q1.head; i < q1.tail; ++i) {//输出小哈手中的牌
cout << q1.data[i] << " ";
}
}
cout << endl;
cout << " 桌子上的牌为:";
for (int i = 1; i <= s.top; ++i) {//输出桌面上的牌
cout << s.data[i] << " ";
}
cout << endl;
}
int main()
{
stack s; queue q1,q2; //定义桌面,小哼,小哈
s.top = 0;//对栈顶指针初始化
q1.head = 1; q1.tail = 1; q2.head = 1; q2.tail = 1;//队列头尾指针的初始化
int count1, count2;//分别记录小哼小哈初始手中牌的个数
cin >> count1 >> count2;
//下面进行输入手中牌的操作
for (int i = 1; i <= count1; ++i) {//输入小哼手中的牌
cin>>q1.data[q1.tail];//读入一个数到队尾
q1.tail++;//队尾指针加一位
}
for (int i = 1; i <= count2; ++i) {//输入小哈手中的牌
cin >> q2.data[q2.tail];
q2.tail++;
}
//下面开始出牌,小哼先出牌,小哈后出牌,没有人手中的牌为空,一直出牌
while (q1.head < q1.tail && q2.head < q2.tail) {
putCard(q1, s);
putCard(q2, s);
}
//下面进行是否赢牌的判断
winGame(q1,q2,s);
return 0;
}
做题感悟:分析好要用到的数据结构,分析好具体的解题步骤。
第4节 链表
#include <iostream>
#include <cstring>
#define ElementType int
using namespace std;
struct node {
ElementType data;//数据域
node* next;//指针域
};
int main()
{
node* head, * p, * q, * t;//head带头结点的头指针,p用于动态申请空间,q用于指示当前所在位置,t用于输出
head = new node();
head->next = NULL;
q = head;
int num, temp;
cout << "请输入数字总数" << endl; cin >> num;
for (int i = 1; i <= num; ++i) {
p = new node();
cin >> p->data;//初始化结点的数据域
p ->next= NULL;//初始化结点的指针域
q->next = p;//把结点串起来
q = p;//将指示指针后移
}
cout << "请输入要插入的值" << endl; cin >> temp;
q = head;//用q向后进行搜索
while (q->next->data < temp) {//当跳出循环时,t指示比在小和大之间
q = q->next;
}
//进行插入
p = new node();
p->data = temp;
p->next = q->next;
q->next = p;
//进行输出
cout << "插入后的数组" << endl;
t = head->next;
while (t != NULL) {
cout << t->data<<" ";
t = t->next;
}
return 0;
}
第5节 模拟链表
第3章 枚举!很暴力
第1节 坑爹的奥数
问题描述:现在小哼又遇到一个稍微复杂一点的奥数题,口口口+口口口=口口口,将数字1~9分别填入9个口中,每个数字只能使用一次使得等式成立。例如173+286=459就是一个合理的组合,请问一共有多少种合理的组合呢?注意:173+286=459与286+173=459是同一种组合!
算法思路:需要用到的数据结构:数组。将各个方块分别枚举一遍,将值存储到数组里,再将出现过的数记录一下。
解题步骤:
首先进行循环枚举操作。
记录1-9是否出现,判断是否是9。
如果是9并判断是否满足条件,满足输出各个位数上的值。
#include <iostream>
#include <cstring>
#define ElementType int
using namespace std;
int main()
{
int a[10], book[10], t, sum = 0,count=0;
//a用来存储9个空格里面的数。book用来存储1-9是否出现。
// sum用来计算出现的总数,count记录满足条件的数字组合
//下面开始逐位枚举
for(a[1]=1;a[1]<=9;++a[1])
for(a[2]=1;a[2]<=9;++a[2])
for(a[3]=1;a[3]<=9;++a[3])
for(a[4]=1;a[4]<=9;++a[4])
for(a[5]=1;a[5]<=9;++a[5])
for(a[6]=1;a[6]<=9;++a[6])
for(a[7]=1;a[7]<=9;++a[7])
for(a[8]=1;a[8]<=9;++a[8])
for (a[9] = 1; a[9] <= 9; ++a[9]) {
//每次新枚举一位就将记录是否出现的s数组初始化
for (int i = 1; i <= 9; ++i) {
book[i] = 0;
}
for (int i = 1; i <= 9; ++i) {//依次判断9个空格上的数。最关键的一步
book[a[i]] = 1;//如果某位出现就标记为1。想要达到的效果,1-9出现的数字即为book的下标。
}
sum = 0;
for (int i = 1; i <= 9; ++i)
sum += book[i];//统计出现的数字数,
//只有当下标1-9对应的book元素均为1,sum==9,才能说明a[1]-a[9]即9个空格上的数字互异。
if (sum == 9 && ((a[1] * 100 + a[2] * 10 + a[3] + a[4] * 100 + a[5] * 10 + a[6]) == (a[7] * 100 + a[8] * 10 + a[9])))//满足了条件
{
count++;
for (int i = 1; i <= 9; ++i) cout << a[i] << " ";
cout << endl;
}
}
cout << "满足条件的数字组合数为"<<count / 2<<endl;
return 0;
}