对于每种数据结构与算法,都应该在理解其基础的情况下,将它的模板背过(找几道典型的题目,一开始不会写,就看懂答案后当做例题背过模板,再自己重新写出,重复以上操作,直到自己可以独立写出题目)——数学题也是这样学习的
如果跟着学校,就会被安排的明明白白,无论你学校的好坏,基本都没救了
CS学习之路只能靠自己!
信息的载体,是描述客观事物的符号,是计算机中可以操作的对象,是能被计算机识 别,并输入给计算机处理的符号集合。
比如我们现在常用的搜索引擎,一般会有网页、图片、视频等分类。 MP3 就是声音数据,图片当然是图像数据,视频就不用说了,而网页其实指的就是全部数 据的搜索,包括最重要的数字和字符等文字数据。
数据最终会以01二进制形式被计算机识别
是组成数据的、有一定意义的基本单位,在计算机中通常作为整体处理。 也被称为记录。
比如,在人类中,什么是数据元素呀? 当然是人了。
畜类呢? 牛、马、羊、鸡、猪、 狗等动物当然就是畜类的数据元素。
一个数据元素可以自若干个数据项组成。
比如人这样的数据元素,可以有眼、耳、鼻、嘴、 手、脚这些数据项,也可以有 姓名、年龄、性别、出生地址、联系电话等数据项,具体有哪些数据项,要视你做的 系统来决定。
数据项是数据不可分割的最小单位。在数据结构这门课程中,我们把数据项定义 为最小单位,是有助于我们更好地解决问题。所以,记住了,数据项是数据的最小单 位。但真正讨论问题时,数据元素才是数据结构中建立数据模型的着眼点。就像我们 讨论一部电影时,是讨论这部电影角色这样的"数据元素",而不是针对这个角色的姓 名或者年龄这样的"数据项"去研究分析。
是性质相同的数据元素的集合,是数据的子集。
什么叫性质相同呢,是指数据元素具有相同数量和类型的数据项,比如,还是刚才的例子,人都有姓名、生日、性别等相同的数据项。
既然数据对象是数据的子集,在实际应用中,处理的数据元素通常具有相同性质 , 在不产生混淆的情况下,我们都将数据对象简称为数据
是相互之间存在一种或多种特定关系的数据元素的集舍。
结构,简单的理解就是关系,比如分子结构,就是说组成分子的原子之间的排列方式。严格点说, 结构是指各个组成部分相互搭配和排列的方式,在现实世界中,不同数据元素之间不是独立的,而是存在特定的关系,我们将这些关系称为结构。
按照视点的不同, 我们把数据结构分为逻辑结构和物理结构。
i. 逻辑结构:是指数据对象中数据元素之间的相互关系
• 集合结构
• 线性结构
• 树形结构
• 图形结构
ii. 物理结构 (很多书中也叫做存储结构,你只要在理解上把官们当一回事就可以了)
是指数据的逻辑结构在计算机中的存储形式。那么根据物理结构的定义,实际上就是如何把数据元素 存储到计算机的存储器中
• 顺序存储
• 链式存储
• 顺序结构
• 分支结构
• 循环结构
是指一组性质相同的值的集合及定义在此集合上的一些操作的总称。
在 C 语言中,按照取值的不同,数据类型可以分为两
• 原子类型:是不可以再分解的基本类型,包括整型、实型、字符型等。(浮点数计算有误差!!!)
• 结构类型:自若干个类型组合而戚,是可以再分解的。例如,整型数组是由若干整型数据组成的。
• 算法具有五个基本特性: 输入、输出、 有穷性、确定性和可行性
• 算法设计的要求:正确性、可读性、健壮性、时间效率高和存储量低
朴素集合论(最原始的集合论)中的定义,即集合是“确定的一堆东西”,集合里的“东西”则称为元素。现代的集合一般被定义为:由一个或多个确定的元素所构成的整体 。
C 数组允许定义可存储相同类型数据项的变量,结构是 C 编程中另一种用户自定义的可用的数据类型,它允许您存储不同类型的数据项。
结构体就是集合在C中的表示,几乎所有的数学概念都是建立在集合的基础上,所有的数据结构都是特殊的集合,所以在C中定义数据结构,必须使用结构体。
代码如下(示例):
//此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
//同时又声明了结构体变量s1
//这个结构体并没有标明其标签
struct
{
int a;
char b;
double c;
} s1;
//此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
//结构体的标签被命名为SIMPLE,没有声明变量
struct SIMPLE
{
int a;
char b;
double c;
};
//用SIMPLE标签的结构体,另外声明了变量t1、t2、t3
struct SIMPLE t1, t2[20], *t3;
//也可以用typedef创建新类型
typedef struct
{
int a;
char b;
double c;
} Simple2;
//现在可以用Simple2作为类型声明新的结构体变量
Simple2 u1, u2[20], *u3;
//此结构体的声明包含了其他的结构体
struct COMPLEX
{
char string[100];
struct SIMPLE a;
};
// --------------------------------------------------------------
//此结构体的声明包含了指向自己类型的指针
struct NODE
{
char string[100];
struct NODE *next_node;
};
// --------------------------------------------------------------
//结构体变量的初始化
#include
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
} book = {"C 语言", "RUNOOB", "编程语言", 123456}; //从这里的 book = {...}也可以看出,结构体就是集合
int main()
{
printf("title : %s\nauthor: %s\nsubject: %s\nbook_id: %d\n", book.title, book.author, book.subject, book.book_id);
}
函数也称映射(两个元素的集之间元素相互“对应”的关系)或变换,函数也是一种算法、操作、算符,映射在计算机中处处存在
对数据结构所有的操作可以归纳为创建、销毁、增删查改
数列、级数、线性表都是序列
顺序表和单链表是重中之重,是后续所有数据结构的基础,必须熟练的掌握,但经过实践学习,很多时候思路是容易的,但是无法写出,都是由于C语言指针、结构体没有掌握,语言基础薄弱。所以在接下来继续学习的路上,我还会花费大量的时间打牢C的基础,完成基础的学习,待熟练之后,再继续推进
树
图
我们给出这样的定义,输入规模 n 在没有限制的情况下,只要超过一个数值 N ,这个函数就总是大于另一个函数,我的称函数是渐近增长的。
常对幂指阶
0(1) < O(logn) < O(n) < O(nlogn) < 0(n2 ) < 0(n3 ) < 0(2") < O(n!) < O(n")
只保留阶数最高项!!!即数量级!!!
忽略常数项,忽略系数,系数化
例题:
例题:
核心语句 m++执行的次数为:(注意双重求和)
C 传递数组给函数
如果想要在函数中传递一个一维数组作为参数,您必须以下面三种方式来声明函数形式参数,这三种声明方式的结果是一样的,因为每种方式都会告诉编译器将要接收一个整型指针。同样地,您也可以传递一个多维数组作为形式参数。
方式 1
形式参数是一个指针(您可以在下一章中学习到有关指针的知识):
void myFunction(int *param)
{
.
.
.
}
方式 2
形式参数是一个已定义大小的数组:
void myFunction(int param[10])
{
.
.
.
}
方式 3
形式参数是一个未定义大小的数组:
void myFunction(int param[])
{
.
.
.
}
实例
现在,让我们来看下面这个函数,它把数组作为参数,同时还传递了另一个参数,根据所传的参数,会返回数组中各元素的平均值:
double getAverage(int arr[], int size)
{
int i;
double avg;
double sum;
for (i = 0; i < size; ++i)
{
sum += arr[i];
}
avg = sum / size;
return avg;
}
现在,让我们调用上面的函数,如下所示:
实例
#include
/* 函数声明 */
double getAverage(int arr[], int size);
int main ()
{
/* 带有 5 个元素的整型数组 */
int balance[5] = {1000, 2, 3, 17, 50};
double avg;
/* 传递一个指向数组的指针作为参数 */
avg = getAverage( balance, 5 ) ;
/* 输出返回值 */
printf( "平均值是: %f ", avg );
return 0;
}
double getAverage(int arr[], int size)
{
int i;
double avg;
double sum=0;
for (i = 0; i < size; ++i)
{
sum += arr[i];
}
avg = sum / size;
return avg;
}
当上面的代码被编译和执行时,它会产生下列结果:
平均值是: 214.400000
您可以看到,就函数而言,数组的长度是无关紧要的,因为 C 不会对形式参数执行边界检查。
题目描述 在X星系的广袤空间中漂浮着许多X星人造“炸弹”,用来作为宇宙中的路标。 每个炸弹都可以设定多少天之后爆炸。
比如:阿尔法炸弹2015年1月1日放置,定时为15天,则它在2015年1月16日爆炸。
有一个贝塔炸弹,a年b月c日放置,定时为n天,请你计算它爆炸的准确日期。
输入
输入存在多组数据,每组数据输入一行,每一行输入四个正整数a,b,c,n
输入保证日期在1000-01-01到2020-01-01之间,且日期合法。 n不超过1000
输出
请填写该日期,格式为 yyyy-mm-dd 即4位年份2位月份2位日期。比如:2015-02-19
请严格按照格式书写。不能出现其它文字或符号。
样例输入 Copy
2015 1 1 15
2014 11 9 1000
样例输出 Copy
2015-01-16
2017-08-05
#include
using namespace std;
#include
bool fff(int year) {
if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) {
//分闰年(29天;4年1闰;闰年能被4整除但不能被100整除,或能被400整除,即四年一闰,百年不闰,四百年再闰。 例如,2000年是闰年,2100年则是平年)
return true;
}
//平年(28天)
return false;
}
int main() {
int ms[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
//数组第一位下标为0,只也为0,这样数组下标就可以与月份相同,便于书写
int y, m, d, n;
while (cin >> y >> m >> d >> n) {
//将n拆成一天一天的加,满一个月(年)就进位
for (int i = 0; i < n; i++) {
//判断现在的年份是否为闰年,修改2月的天数
if (fff(y)) {
ms[2] = 29;
} else {
ms[2] = 28;
}
//满一个月,进一月
if (++d > ms[m]) {
m++;
d = 1;
}
//满一年,进一个年
if (m > 12) {
y++;
m = 1;
}
}
printf("%d-%02d-%02d\n", y, m, d);//%02d表示占两个位置,如果位置不够,就用0来占。
}
return 0;
}
#include
int main ()
{
int var_runoob = 10;
int *p; // 定义指针变量
p = &var_runoob; //p就是指针,而不能认为*p是指针;*p是p所指向的变量
printf("var_runoob 变量的地址: %p\n", p);
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
var_runoob 变量的地址: 0x7ffeeaae08d8
递增一个指针
我们喜欢在程序中使用指针代替数组,因为变量指针可以递增,而数组不能递增,数组可以看成一个指针常量。下面的程序递增变量指针,以便顺序访问数组中的每一个元素:
实例
#include
const int MAX = 3;
int main ()
{
int var[] = {10, 100, 200};
int i, *ptr;
/* 指针中的数组地址 */
ptr = var; //将数组的首地址赋值给指针ptr
for ( i = 0; i < MAX; i++)
{
printf("存储地址:var[%d] = %p\n", i, ptr );
printf("存储值:var[%d] = %d\n", i, *ptr );
/* 指向下一个位置 */
ptr++;//ptr指向一片连续的内存(即数组),所以可以进行递增或递减运算
}
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
存储地址:var[0] = e4a298cc
存储值:var[0] = 10
存储地址:var[1] = e4a298d0
存储值:var[1] = 100
存储地址:var[2] = e4a298d4
存储值:var[2] = 200
double balance[50];
balance 是一个指向 &balance[0] 的指针,即数组 balance 的第一个元素的地址。因此,下面的程序片段把 p 赋值为 balance 的第一个元素的地址:
double *p;
double balance[10];
p = balance;
使用数组名作为常量指针是合法的,反之亦然。因此,*(balance + 4) 是一种访问 balance[4] 数据的合法方式。
可能有一种情况,我们想要让数组存储指向 int 或 char 或其他数据类型的指针。下面是一个指向整数的指针数组的声明:
int *ptr[MAX];
在这里,把 ptr 声明为一个数组,由 MAX 个整数指针组成。因此,ptr 中的每个元素,都是一个指向 int 值的指针。下面的实例用到了三个整数,它们将存储在一个指针数组中,如下所示:
实例
#include
const int MAX = 3;
int main ()
{
int var[] = {10, 100, 200};
int i, *ptr[MAX];
for ( i = 0; i < MAX; i++)
{
ptr[i] = &var[i]; /* 赋值为整数的地址 */
}
for ( i = 0; i < MAX; i++)
{
printf("Value of var[%d] = %d\n", i, *ptr[i] );
}
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
Value of var[0] = 10
Value of var[1] = 100
Value of var[2] = 200
您也可以用一个指向字符的指针数组来存储字符串列表,如下:
实例
#include
const int MAX = 4;
int main ()
{
const char *names[] = {
"Zara Ali",
"Hina Ali",
"Nuha Ali",
"Sara Ali",
};
int i = 0;
for ( i = 0; i < MAX; i++)
{
printf("Value of names[%d] = %s\n", i, names[i] );
}
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
Value of names[0] = Zara Ali
Value of names[1] = Hina Ali
Value of names[2] = Nuha Ali
Value of names[3] = Sara Ali
如下所示:
实例
#include
/* 函数声明 */
double getAverage(int *arr, int size);
int main ()
{
/* 带有 5 个元素的整型数组 */
int balance[5] = {1000, 2, 3, 17, 50};
double avg;
/* 传递一个指向数组的指针作为参数 */
avg = getAverage( balance, 5 ) ;
/* 输出返回值 */
printf("Average value is: %f\n", avg );
return 0;
}
double getAverage(int *arr, int size)
{
int i, sum = 0;
double avg;
for (i = 0; i < size; ++i)
{
sum += arr[i];
}
avg = (double)sum / size;
return avg;
}
当上面的代码被编译和执行时,它会产生下列结果:
Average value is: 214.40000
**注意:**不能直接传递数组int balance[],因为int balance[]是一个数组,只有数组名才是指针
指向结构的指针
您可以定义指向结构的指针,方式与定义指向其他类型变量的指针相似,如下所示:
struct Books *struct_pointer;
现在,您可以在上述定义的指针变量中存储结构变量的地址。为了查找结构变量的地址,请把 & 运算符放在结构名称的前面,如下所示:
struct_pointer = &Book1;
为了使用指向该结构的指针访问结构的成员,您必须使用 -> 运算符(自带解引用),如下所示:
struct_pointer->title;
#include
#include
#define MaxSize 10
typedef struct {
int data[MaxSize];//用静态的“数组”存放元素
int length;//顺序表的当前长度
}SqList;
//基本操作:创建并初始化一个顺序表
void InitList(SqList *L) {
for(int i=0;i<MaxSize;i++){
L->data[i]=0; //默认初始化顺序表元素都为0
L->length=0; //默认初始化顺序表长度为0
}
}
int main() {
SqList *L; //声明一个顺序表指针L
InitList(L);//将指针L传入初始化函数
return 0;
}
#include
#include
#define InitSize 10 //默认的最大长度
typedef struct {
int *data;//数组名data作为动态分配数组的指针
int MaxSize;//顺序表的最大容量
int length;//顺序表的当前长度
}SqList;
//基本操作:创建并初始化一个顺序表
void InitList(SqList *L) {
//用malloc()申请一片连续的内存空间,使data指向申请空间的首地址
L->data=(int*)malloc(InitSize*sizeof(int));
L->MaxSize=InitSize;
L->length=0;
}
//基本操作:动态增加数组的长度 (可变数组)
void IncreaseSize(SqList *L,int len) {
//让p指向data,即可变数组的首地址,目的是为了copy一份L,未来可以通过free(p)来回收原来L的内存
int *p=L->data;
//新分配一块更大的内存空间
L->data=(int*)malloc((L->MaxSize+len)*sizeof(int));
//更新L->length的大小
L->length= L->length+len;
//将原来的数据copy到新分配的空间里
for(int i=0;i<L->length;i++) {
L->data[i]=p[i];
}
L->MaxSize=L->MaxSize+len;
//释放原来可变数组的内存
free(p);
}
int main() {
SqList *L; //声明一个顺序表指针L
InitList(L);//将指针L传入初始化函数
IncreaseSize(L,5);//将可变数组长度加5
return 0;
}
bool ListInsert(SqList *L,int i,int e) {
//将e插入到顺序表L中第i个位置
if(i<1||i>L->length+1) //判断i是否合法
return false;
if(L->length>=MaxSize) //当前存储空间已满,不能插入
return false;
for(int j=L->length;j>=i;j--) {
L->data[i-1]=L->data[j-1]; //将第i个元素及其之后的元素后移
}
L->data[i-1]=e; //插入e
L->length++;
return true;
}
bool ListDelete(SqList *L,int i,int e) {
//删除顺序表L中第i位元素
if(i<1||i>L->length) //判断i是否合法
return false;
e=L->data[i-1]; //将被删除的元素赋值给e
for(int j=i;j<L->length;j++) {
//将第i个位置之后的元素前移
L->data[j-1]=L->data[j];
}
L->length--; //表长-1
return true;
}
int LocateElem(SqList *L,int e) {
//按值查找顺序表中元素e,如果成功,就返回元素位序,否则返回0
for(int i=0;i<L->length;i++) {
if(L->data[i]==e) return i+i; //下标为i的元素=e,返回其位序i+1
}
return 0; //查找失败
}
由于顺序表的插入、删除操作需要移动大量的元素,影响效率,故引入链表:对线性表的插入、删除就不需要移动元素了,只需要修改指针;但是查找操作就需要从头开始查找线性表
#include
#include
typedef struct LNode {
int data; //数据域
struct LNode* next; //指向下一个节点的指针域
}LNode, *LinkList;
//基本操作:初始化一个空的(带头结点)单链表
bool InitList(LinkList L) {
L = (LNode*)malloc(sizeof(LNode));
if(L == NULL) //空间不足,分配失败
return false;
else
L->next = NULL; //头结点之后还没有结点
}
//基本操作:(后插法)按位序插入(带头结点)
bool ListInsert(LinkList L,int i,int e) {
if(i<1) { //位序i不合法
return false;
}
LNode *p = L; //指针指向当前扫描的结点
int j = 0; //当前指针指向第几个结点
while(p!=NULL&&j<i-1) { //循环找到第i-1个结点
p=p->next;
j++;
}
if(p==NULL) { //i值不合法
return false;
}
else
LNode* s = (LNode *)malloc(sizeof(LNode));
s->data=e;
s->next=p->next;
p->next=s;
return true;
}
//基本操作:(前插法)按位序插入(带头结点)
bool InsertPriorNode(LinkList L,LNode *p,int e) {
if(p==NULL) {
return false;
}
LNode *s = (LNode *)malloc(sizeof(LNode));
if(s==NULL) //内存分配失败
return false;
s->next=p->next;
p->next=s;
s->data=p->data;
p->data=e;
return true;
}
//基本操作:(前插法)按位序删除(带头结点)
bool ListDelete(LinkList L,int i,int e) {
if(i<1)
return false;
else
LNode *p=L; //指针p指向头结点
int j=0; //当前结点指向第几结点
while(p!=NULL&j<i-1) {
p=p->next;
j++;
}
if(p==NULL) //i值不合法
return false;
if(p->next=NULL) //第i-1个结点后已经没有其他结点
return false;
LNode *q=p->next; //p找到应删除的结点后,用q指向被删除结点
e = q->data; //用e返回元素的值
p->next=q->next; //断开
free(p);
return true;
}
//基本操作:头插法建立单链表(带头结点)
//应用:单链表的逆置
LinkList List_HeadInsert(LinkList L) {
LNode *s;
int x; //输入数据
L=(LinkList)malloc(sizeof(LNode));//创建头结点
scanf("%d",&x);
while(x!=9999) { //输入9999表示结束
s=(LNode *)malloc(sizeof(LNode));//创建新结点
s->data=x;
s->next=L->next;
L->next=s;
scanf("%d",&x);
}
return L;
}
//基本操作:尾插法建立单链表(带头结点)
LinkList List_TailInsert(LinkList L) {
int x;
L=(LNode *)malloc(sizeof(LNode));//创建头结点
LNode *s,*r=L //创建头尾指针
scanf("%d",&x);
while(x!=9999) {
s=(LNode *)malloc(sizeof(LNode));//创建新结点
s->data=x;
r->next=s;
r=s;
scanf("%d",&x);
}
r->next=NULL;
return L;
}
int main() {
}
#include
#include
typedef struct LNode {
int data; //数据域
struct LNode *prior,*next; //指向下一个节点的指针域
}DNode, *DLinkList;
//基本操作:初始化双链表
bool InitDLinkList(DLinkList L) {
L=(DNode*)malloc(sizeof(DNode));//创建头结点
if(L==NULL) {
return false;
}
else
L->prior=NULL;
L->next=NULL;
return true;
}
//基本操作:双链表的后插法
bool InsertNextDNode(DNode *p,DNode *s) {//将结点*s插到p后
if(p==NULL || s==NULL) return false;
s->next=p->next;
if(p->next !=NULL)
p->next->prior=s;
s->prior=p;
p->next=s;
return true;
}
int main() {
}
#include
#include
typedef struct LNode{
int data;
struct LNode *next;
}LNode,*LinkList;
//初始化循环单链表
bool InitList(LinkList L) {
L=(LNode*)malloc(sizeof(LNode));//创建头结点
if(L==NULL) return false;
L->next=L;//头结点next指向头结点
return true;
}
int main() {
}
例题:算法复杂度
例题:线性表的存储结构选择
例题:判空条件
例题:线性表的存储结构选择
例题:线性表的存储结构选择
例题:链接地址(next域中存储了什么)
例题:线性表的存储结构选择
栈的抽象数据类型是由以下结构和操作定义的。堆栈是结构化的,如上面所描述的,栈是一个有序的项的集,项添加和删除的一端称为“顶”。栈的命令是按后进先出进行的。栈的操作如下:
Stack()创建一个新的空栈。它不需要参数,并返回一个空栈。
Push(item)将新项添加到堆栈的顶部。它需要参数item并且没有返回值。
pop()从栈顶删除项目。它不需要参数,返回item。栈被修改。
peek()返回栈顶的项,不删除它。它不需要参数。堆栈不被修改。
isEmpty()测试看栈是否为空。它不需要参数,返回一个布尔值。 size()返回栈的项目数。它不需要参数,返回一个整数。
#include
#include
#define MaxSize 10
//顺序栈的定义
typedef struct {
int data[MaxSize];
int top;
}SqStack;
//初始化顺序栈
void InitStack(SqStack *s) {
s->top=-1; //初始化栈顶指针(标记)
}
//新元素入栈
bool Push(SqStack *s,int x) {
if(s->top==MaxSize-1) return false;
else {
s->top=s->top+1;
s->data[s->top]=x;
return true;
}
}
//出栈操作
bool Pop(SqStack *s,int x) {
if(s->top==-1) return false;
else {
x=s->data[s->top];
s->top=s->top-1;
return true;
}
}
int main() {
}
对于循环队列取余运算的总结:
以本题为例:
静态数组的MaxSize=10,即数组长度大小一定
一开始插入三个元素,在逻辑上不看做循环队列也是也可以的,如下图:
但是当前4个元素出队列了,之后又新增了2个元素入队列(如下图),就会产生假溢出现象,即数组并没有完全利用,但是已经无法再入队了。
这时如果将队列在逻辑上看做循环队列,如下图,则rear指针可以从头再次添加队尾元素,由取余运算的意义:MaxSize=10等价于头尾指针可以在(0,1,2,3,4,5,6, 7,8,9)序列内循环的取值,这时就可以无视头尾指针在真正的数组上的前后顺序,它们在逻辑上(循环队列)永远是有先后顺序的。
//定义顺序队列
typedef struct {
int data[MaxSize];
int front,rear;
}SqQueue;
//初始化队列
void InitQueue(SqQueue *Q) {
//初始时 队头、队尾指针指向0
Q->rear=0;
Q->front=0;
}
//入队
bool EnQueue(SqQueue *Q,int x) {
if((Q->rear+1)%MaxSize==Q->front) //队满
return false;
else
Q->data[Q->rear]=x;//将x插入队尾
Q->rear=(Q->rear+1)%MaxSize;//队尾指针后移 (逻辑上看做循环队列)
return true;
}
//出队
bool DnQueue(SqQueue *Q,int x) {
if(Q->rear==Q->front) return false;//队空则报错
else
x=Q->data[Q->front];
Q->front=(Q->front+1)%MaxSize;
return true;
}
#include
#include
#define MaxSize 10
//链队列就是操作上受限的单链表
//链队列的定义
typedef struct LNode { //链队列的结点
int data;
struct LNode* next;
}LinkNode;
typedef struct { //链式队列
LinkNode *front,*rear; //队列的队头和队尾指针 s
}LinkQueue;
//初始化(带头结点)
void InitQueue(LinkQueue *Q) {
Q->front=Q->rear=(LinkNode*)malloc(sizeof(LinkNode));
Q->front->next=NULL;
}
//队列判空(带头结点)
bool IsEmpty(LinkQueue Q) {
if(Q.front==Q.rear) return true;
else return false;
}
//入队(带头结点)
void EnQueue(LinkQueue *Q,int x) {
LinkNode *s=(LinkNode*)malloc(sizeof(LinkNode));
s->data=x;
s->next=NULL;
Q->rear->next=s;
Q->rear=s;
}
//出队(带头结点)
bool DeQueue(LinkQueue *Q,int x) {
if(Q->front==Q->rear) return false; //空队列
else {
LinkNode *p=Q->front->next; //用p指向待删除的结点
x=p->data; //用x值返回删除结点的值
Q->front->next=p->next; //修改头结点的next指针
if(Q->rear==p) //本次为最后一个结点出队
Q->rear=Q->front; //修改rear
free(p);
}
return true;
}
//------------------------------------------------------------------
//初始化(不带头结点)
void InitQueue2(LinkQueue *Q) {
Q->front=NULL;
Q->rear=NULL;
}
//队列判空(不带头结点)
bool IsEmpty2(LinkQueue Q) {
if(Q.front==NULL) return true;
else return false;
}
//入队(不带头结点)
void EnQueue2(LinkQueue *Q,int x) {
LinkNode *s=(LinkNode*)malloc(sizeof(LinkNode));
s->data=x;
s->next=NULL;
if(Q->front==NULL) { //空队列中插入第一个元素
Q->front=s;
Q->rear=s;
} else {
Q->rear->next=s; //新结点插到rear之后
Q->rear=s;
}
}
//出队(不带头结点)
bool DeQueue2(LinkQueue *Q,int x) {
if(Q->front==NULL) return false; //空队
LinkNode *p=Q->front; //p指向本次出队的结点
x=p->data; //x值返回删除结点的值
Q->front=p->next; //修改front
if(Q->rear==p) { //此次为最后一个结点
Q->front=NULL;
Q->rear=NULL;
}
free(p);
return true;
}
int main() {
}
#include
#include
#define MAXSIZE 10
//顺序查找(线性查找),主要用于线性表的查找,顺序存储与链式存储均可以
//无序线性表的顺序查找(哨兵)
typedef struct {
int *elem; //用指针表示数组
int TableLen; //线性表长度
}SSTable;
int Search_Seq(SSTable *ST,int key) {
ST->elem[0]=key; //线性表第一个设为查找关键字,即哨兵
int i=ST->TableLen-1;
while(ST->elem[i]!=key) {
i--;
}
return i;
}
int main() {
SSTable *L=(SSTable *)malloc(sizeof(SSTable));
L->elem=(int *)malloc(sizeof(int)*MAXSIZE);
L->TableLen=MAXSIZE;
for(int i=0;i<L->TableLen;i++) {
L->elem[i]=i;
}
for(int i=0;i<L->TableLen;i++) {
printf("%d\n",L->elem[i]);
}
printf("-------------\n");
int result = Search_Seq(L,3);
printf("%d",result);
return 0;
}
//折半查找(二分查找),仅适用于有序顺序表(仅适用于数组)
typedef struct {
int *elem; //用指针表示数组
int TableLen; //线性表长度
}SSTable;
int Binary_Search(SSTable *L,int key) {
int low=0;
int high=L->TableLen-1;
int mid;
while(low<=high) {
mid=(low+high)/2;
if(L->elem[mid]==key) return mid;
else if(L->elem[mid]>key) high=mid-1;
else low=mid+1;
}
return -1;
}
void InsertSort(int A[],int n) {
int i,j;
for(i=2;i<=n;i++) {
A[0]=A[j];//哨兵
for(j=i-1;A[0].key<A[j].key;j--)
A[j+1]=A[j];
A[j+1]=A[0];
}
}
或者
//排序算法
#include
#include
#define MAXSIZE 10
typedef struct SqList {
int* elem;
int Len;
} SqList, *List;
//直接插入排序
//适用于顺序表和链表
//思想:每次将一个待排序的元素按大小插入到前面已经排好序的序列中
void InsertSort(List L,int n) {
//i为序列中待比较元素的下标
int i,j;
for(i=2; i<=n; i++) {
//第一个元素位置不使用,作为哨兵
//将待排序的元素elem[i],与前面已排序的序列elem[i-1]做比较
if(L->elem[i]<L->elem[i-1]) {
//elem[0]是交换的中间变量,即哨兵,开始的时候不存放任何元素
//将每一次的带排序元素赋值给哨兵
L->elem[0]=L->elem[i];
for(j=i-1; L->elem[0]<L->elem[j]; j--)
//待排序元素elem[0]比之前已经排好的序列elem[j]小,将之前已经排好的序列elem[j]向后移动一个单元
L->elem[j+1]=L->elem[j];
//将哨兵elem[0]插入到正确的位置
L->elem[j+1]=L->elem[0];
}
}
}
int main() {
List L=(List)malloc(sizeof(List));
L->elem=(int*)malloc(sizeof(int)*MAXSIZE);
L->Len=MAXSIZE;
SqList* p=L;
int i=100;
for(int j=1; j<L->Len; j++) {
p->elem[j]=i;
i--;
}
for(int i=1; i<L->Len; i++) {
printf("%d\n",p->elem[i]);
}
InsertSort(L,MAXSIZE);
printf("-----------------------\n");
for(int i=1; i<L->Len; i++) {
printf("%d\n",p->elem[i]);
}
return 0;
}
例题
首先A【0】处空出来(哨兵),此时待插入的数字为A【2】(i<=2开始的);
将A【2】的值6赋值到A【0】处(哨兵);接着进入了第二层for循环,此时
j=1;故比较哨兵与A【1】的值,结果没有发生变化;
第二趟排序,将7插入到哨兵的位置(此时i=3);进入第二层for循环,此时j=2,即比较6与7的大小,任然没有变化,跳出for循环
第三趟排序,进入第二层for循环,发现2均小于已经排好序的序列元素,故一直元素往后移动,直到A【0】.key
最后进行赋值操作A[j+1]=A[0];剩下的仿上可得解
直接插入排序是在顺序查找的基础上进行的排序;将顺序查找修改为折半查找就得到了折半插入排序
void BubbleSort(int A[],int n) {
for(int i=0;i<n-1;i++) {
bool flag=false;
for(int j=n-1;j>i;j--) //从后向前比较
//判断两个相邻的元素是不是逆序
if(A[j-1].key>A[j].key) {
swap(A[j-1],A[j]);
flag = true; // flag若为true,则证明有逆序
}
if(flag==false) return;// flag若为false,则证明没有任何一对序列为逆序,即已经排好了
}
}
int Partition(int A[],int low,int high) {
int pivot = A[low];
while(low<high) {
while(low<high&&A[high]>=pivot)
high--;
A[low]=A[high];
while(low<high&&A[low]<=pivot)
low++;
A[high]=A[high];
}
A[low]=pivot;
return low;
}
void QuickSort(int A[],int low,int high) {
if(low<high) {
int pivotpos = Partition(A,low,high);
QuickSort(A,low,pivotpos-1);
QuickSort(A,pivotpos+1,high);
}
}
void SelectSort(int A[],int n) {
for(int i=0;i<n-1;i++) {
int min=i;
for(int j=i+1;j<n;j++)
if(A[j]<A[min])
min=j;
if(min!=i) swap(A[i],A[min]);
}
}
(一式)结点的总数=度为0的结点数+度为1的结点数+度为2的结点数
(二式)结点的总数=度为0的结点,其孩子结点总数(为0)+度为1的结点,其孩子结点总数(为1n)+度为2的结点,其孩子结点总数(为2n)+根结点
联立两式得解,性质1
typedef struct BiTNode {
int data;
struct BiTNode *lchild,*rchild;
}BiTNode,*BiTNode;
//二叉树的先序遍历
void PreOrder(BiTNode T) {
if(T!=NULL) {
visit(T);
PreOrder(T->lchild);
PreOrder(T->rchild);
}
}
//二叉树的中序遍历
void InOrder(BiTNode T) {
if(T!=NULL) {
InOrder(T->lchild);
visit(T);
InOrder(T->rchild);
}
}
//二叉树的后序遍历
void PostOrder(BiTNode T) {
if(T!=NULL) {
PostOrder(T->lchild);
PostOrder(T->rchild);
visit(T);
}
}
//二叉树的中序遍历(非递归)
void InOrder2(BiTNode T) {
InitStack(S);
BiTree p = T;
while(p||!IsEmpty(S)) {
if(p) {
Push(S,p);
p=p->lchild;
} else {
Pop(S,p);
visit(p);
p=p->rchild;
}
}
}
//二叉树的层次遍历
void levelOrder(BiTNode T) {
InitQueue(Q);
BiTree p;
EnQueue(Q,p);
while(!isEmpty Q) {
DeQueue(Q,p);
visit(p);
if(p->lchild!=NULL) EnQueue(Q,p->lchild);
if(p->rchild!=NULL) EnQueue(Q,p->rchild);
}
}
先在先序遍历序列里找到根结点(第一个元素),根结点(第一个元素)将中序遍历序列划分为两个部分(425,63),分别在左子树和右子树
然后在先序遍历序列里找到左子树根结点(第二个元素),根结点(第二个元素)将中序遍历序列又划分为两个部分(4,5),分别在左子树和右子树
再看右子树的部分,在先序遍历序列里找到右子树根结点(6,3)中先出现的元素,即(3),故右子树根结点就是(3),最后(6)在左子树
//线索二叉树
typedef struct ThreadNode {
int data;
struct ThreadNode *lchild,*rchild;
int ltag,rtag;
}ThreadNode,* ThreadTree;
//中序线索二叉树线索化
void InThread(ThreadTree &p,ThreadTree &pre) {
if(p!=NULL) {
InThread(p->lchild,pre);
if(p->lchild==NULL) {
p->lchild = pre;
p->ltag = 1;
}
if(pre!=NULL&&pre->rchild==NULL) {
pre->rchild = p;
pre->rtag = 1;
}
pre=p;
InThread(pre->rchild,pre);
}
}
//中序线索二叉树的遍历
//找前驱结点
ThreadNode *FirstNode(ThreadNode *p) {
while(p->ltag==0) p=p->lchild;
return p;
}
//找后继结点
ThreadNode *NextNode(ThreadNode *p) {
if(p->rchild==0) return FirstNode(p->rchild);
else return p->rchild;
}
#define MAX_TREE_SIZE 100
//双亲表示法
typedef strct {
int data;
int parent;
}PTNode;
typedef struct {
PYNode nodes[MAX_TREE_SIZE];
int n;
}PTree;
//孩子表示法
typedef struct {
int child; //孩子结点的下标
struct CNode *next; //下一个结点的指针
}CNode;
typedef struct {
int data; //每个结点存放的元素
struct CNode *child;//单链表的头指针
}PNode;
typedef struct {
PNode nodes[MAX_TREE_SIZE];
int n; //该颗树的结点数量
}CTree;
//(左)孩子(右)兄弟表示法(类似于二叉链表)
typedef struct CSNode {
int data;
struct CSNode *firstchild, *nextsibling;
}CSNode,CSTree;
用树表示10个子集s0-s9,即10个根结点,将它们用孩子表示法存储,即保存元素的数据域data,和一个保存双亲结点下标的伪指针域parent(均为-1)
若变为了3个子集,s0-s2,同理,则变为以下3棵树:根结点parent的绝对值的大小表示该棵树结点的个数
基本操作
//并查集
int UFSets[SIZE];//默认数据与数组下标一致
void Initial(int S[]) {
//每个元素都初始化成一个子集
for(int i=0;i<size;i++) {
S[i]=-1; //(因为每个元素都为一个子集)将所有的双亲结点的下标都修改为1
}
}
int Find(int S[],int x) { //传入并查集S,和查找元素x
while(S[x]>=0) x=S[x]; //如果是正数,说明不是根结点,继续循环查找
return x;
}
void Union(int S[],int Root1,int Root2) { //传入并查集S和两个子集根结点的下标
S[Root2]=Root1;
}
例题:Union操作
//二叉排序(查找)树
//查找
BTSNode *BST_Search(BiTree T,int key,BSTNode *p) {
p = NULL;
while(T!=NULL&&key!=T->data) {
p=T; //修改双亲结点为当前结点
if(key<T->data) T=T->lchild;
else T=T->rchild;
}
return T;
}
//插入
bool BST_Insert(BiTree &T,int k) {
if(T==NULL) { //半段二叉树是否为空,若为空,则申请一块区域
T=(BiTree)malloc(sizeof(BSTNode));
T->key = k; //结点赋值为k
T->lchild = T->rchild = NULL; //左右孩子结点赋值为空
return true;
}
else if(k==T->key) return false; //该值等于根结点,不插入
else if(k<T->key) return BST_Insert(T->lchild,k); //若小于根结点的值,则插入到左孩子结点
else return BST_Insert(T->rchild,k);//若大于根结点的值,则插入到右孩子结点
}
//构造二叉排序树
void Creat_BST(BiTree &T,int str[],int n) {
//传入:要构造的树,所有要插入元素的数组,插入结点的数量
T=NULL;
int i=0;//计数器,计数插入结点的数量
while(i<n) {
BST_Insert(T,str[i]);//循环调用插入函数(见上)
i++;
}
}
//平衡二叉树的判断
viod Judege_AVL(BiTree bt,int *balance,int *h) {
//传入参数:根结点bt,平衡性,高度 (传入指针就可以在函数内部修改了)
int bl = 0;//左子树平衡性
int br = 0;//右子树平衡性
int hl = 0;//左子树高度
int hr = 0;//右子树高度
if(bt==NULL) {
h=0;
balance=1;
}
else if(bt->lchild==NULL&&bt->rchild==NULL) {
h=1;
balance=1;
}
else {
Judege_AVL(bt->lchild,bl,hl);
Judege_AVL(bt->rchild,br,hr);
if(hl>hr) h=hl+1;
else h=hr+1;
if(abs(hl-hr)<2&&bl==1&&br==1) balance=1;
else balance=0;
}
}
例题:有向图的存储
例题:无向图的存储(对称矩阵,可以压缩存储)
例题:网的存储
#deine MaxVertexNum 100
//邻接矩阵法
typedef char VertexType;//结点类型
typedef int EdgeType; //边的类型
typedef struct {
VertexType Vex[MaxVertexNum]; //结点集
EdgeType Edge[MaxVertexNum][MaxVertexNum]; //边集
int vexnum,arcnum; //结点数与边数
}MGraph;
//邻接表法
//定义边表结点
typedef struct ArcNode {
int adjvex; //下个顶点的数组下标
struct ArcNode *next; //指向下一个链表结点的指针域
//若有权重,也可以加上权重
}ArcNode;
//定义顶点表
typedef struct VNode {
VertexType data; //该顶点的数据域
ArcNode *first; //指向属于它边表的头指针
}VNode,AdjList[MaxVertexNum]; //数组就是顶点表
//定义邻接表
typedef struct {
AdjList vetices; //所有的顶点
int vexnum,arcnum; //结点数与边数
}ALGraph;
//十字链表法
//定义边表结点
typedef struct ArcNode {
int tailvex,headvex; //尾域和头域(数组下标)
struct ArcNode *hlink, *tlink; //出弧单链表和入弧单链表
//若有权重,也可以加上权重
}ArcNode;
//定义顶点表结点
typedef struct VNode {
VertexType data; //该顶点的数据域
ArcNode *firstin, *firstout; //出弧单链表和入弧单链表的头指针
};
//定义十字链表
typedef struct {
VNode xlist[MaxVertexNum]; //数组就是顶点表
int vexnum,arcnum; //结点数与边数
}GLGraph;
//邻接多重表
//定义边结点
typedef struct ArcNode {
int ivex ,jvex; //两个端点
struct ArcNode *ilink, *jlink; //第一个端点的边表和第二个端点的边表
//若有权重,也可以加上权重
//bool mark; //作为标记
};
//定义顶点表结点
typedef struct VNode {
VertexType data; //该顶点的数据域
ArcNode *firstedge; //指向对应边表的头指针
};
//定义邻接多重表
typedef struct {
VNode adjlist[MaxVertexNum]; //数组就是顶点表
int vexnum,arcnum; //结点数与边数
}AMLGraph;
因为图中存在“环”,所以不能完全按照树的层次遍历算法,这样“顶点5”就会被访问两次,需要借助队列和辅助标记数组
//广度优先搜索
bool visited[MAX_TREE_SIZE]; //初始化辅助数组为FALSE
//访问不连通的结点
void BFSTraverse(Graph G) {
for(int i=0;i<G.vexnum;++i) visited[i]=FALSE;
InitQueue(Q);
for(int i;i<G.vexnum;i++) {
if(!visit[i]) BFS(G,i);
}
}
void BFS(Graph G,int v) { //传入图G和初始的顶点
visit(v); //访问这个顶点
visited[v] = TRUE; //辅助数组下标修改为TRUE,防止重复访问(改进树的层次遍历)
EnQueue(Q,v); //将v顶点入队到队列Q中
while(!isEmpty(Q)) { //循环到队列为空
DeQueue(Q,v); //出队队首元素
for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w)) {
if(!visited[w]) {
visit[v];
visit[w]=TRUE;
EnQueue(Q,w);
}
}
}
}
应用:利用广度优先搜索思想无权图单元最短路径
与深度优先搜素不同,BFS总是优先搜索离初始状态最近的状态。(二叉树的层序遍历)
DFS利用栈
BFS利用队列
例题1:
应用: 广度优先生成树
//深度优先搜索
bool visited[MAX_TREE_SIZE]; //初始化辅助数组为FALSE
//访问不连通的结点
void DFSTraverse(Graph G) {
for(int i=0;i<G.vexnum;++i) visit[i]=FALSE;
for(int i=0;i<G.vexnum;++i) if(!visited[i]) DFS(G,i); //类似于树的先序遍历,采用递归
}
void DFS(Graph G,int v) { //传入图G和初始的顶点
visit(v); //访问这个顶点
visited[v]=TRUE; //辅助数组下标修改为TRUE,防止重复访问(改进树的先序遍历)
for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w)) if(!visited[v]) DFS(G,w);
}
例题:深度优先生成树
例题:遍历与连通性
有向图中没有上述两个结论
从某个状态开始,不断地转移状态直到无法转移,然后回退到前一步的状态;继续重复上述过程
利用了栈
用递归实现更简单
例题1:
例题2:
例题:[蓝桥杯2015初赛]方程整数解
题目描述 方程: a^2 + b^2 + c^2 = 1000 这个方程有正整数解吗?有:a,b,c=6,8,30 就是一组解。 求出
a^2 + b^2 + c^2 = n(1<=n<=10000)的所有解,解要保证c>=b>=a>=1。
输入
存在多组测试数据,每组测试数据一行包含一个正整数n(1<=n<=10000)
输出
如果无解则输出"No Solution"。 如果存在多解,每组解输出1行,输出格式:a b c,以一个空格分隔按照a从小到大的顺序输出,如果a相同则按照b从小到大的顺序输出,如果a,b都相同则按照c从小到大的顺序输出。
样例输入 Copy
4
1000
样例输出 Copy
No Solution
6 8 30
10 18 24
#include
#include
#include
#include
#include
using namespace std;
int n,a,b,c;
void fun(){
int flag = 0;
for(a = 1; a < sqrt(n); a++){
for(b = a; b < sqrt(n); b++){
for(c = b; c < sqrt(n);c++){
if(a*a+b*b+c*c==n){
flag = 1;
cout << a<<" "<<b<<" "<<c<<endl;
}
}
}
}
if(flag == 0){
cout << "No Solution" << endl;
}
}
int main () {
while(cin >> n){
fun();
}
return 0;
}
例题:
[蓝桥杯2015初赛]牌型种数
题目描述 小明被劫持到X赌城,被迫与其他3人玩牌。 一副扑克牌(去掉大小王牌,共52张),均匀发给4个人,每个人13张。
这时,小明脑子里突然冒出一个问题: 如果不考虑花色,只考虑点数,也不考虑自己得到的牌的先后顺序
自己手里能拿到的初始牌型组合一共有多少种呢?
输出
请输出该整数,不要输出任何多余的内容或说明文字。
思路一:暴力(超时)
首先想到的就是暴力解决,简单粗暴,不费脑子,最容易想到,
方法就是从1~K 一共13张牌,13个循环即可。
#include
int main ()
{
int a[13]; //用来表示牌的种类
int num=0; //用来存放总的种数
for(a[0]=0; a[0]<=4; a[0]++) //每种点数的牌被抽到的张数有五种:0,1,2,3,4
{
for(a[1]=0; a[1]<=4; a[1]++)
{
for(a[2]=0; a[2]<=4; a[2]++)
{
for(a[3]=0; a[3]<=4; a[3]++)
{
for(a[4]=0; a[4]<=4; a[4]++)
{
for(a[5]=0; a[5]<=4; a[5]++)
{
for(a[6]=0; a[6]<=4; a[6]++)
{
for(a[7]=0; a[7]<=4; a[7]++)
{
for(a[8]=0; a[8]<=4; a[8]++)
{
for(a[9]=0; a[9]<=4; a[9]++)
{
for(a[10]=0; a[10]<=4; a[10]++)
{
for(a[11]=0; a[11]<=4; a[11]++)
{
for(a[12]=0; a[12]<=4; a[12]++)
{
if(a[0]+a[1]+a[2]+a[3]+a[4]+a[5]+a[6]+a[7]+a[8]+a[9]+a[10]+a[11]+a[12]==13)
{
num++;
}
}
}
}
}
}
}
}
}
}
}
}
}
}
printf("%d",num);
return 0;
}
思路二:递归
然后就要考虑递归来写了。
递归函数要包含递归主题,递归出口(判断递归结束的条件)。
递归思想:由于不论顺序,所以假定从A开始选择个数(0~4)直到 K(13)。
具体思路见如下代码。
#include
int num=0; //we表示种类数
//n表示每次选择的dian点的牌的数量
//dian表示牌的点数
//sum为13,即牌每个人发13张
void my(int n,int dian,int sum)
{
sum+=n;
if(sum==13)
{
num++;
sum=0;
return;
}
else if(sum>13)
{
return;
}
if(dian==13)
{
return;
}
for(int i=0;i<=4;i++)
{
my(i,dian+1,sum);
}
return;
}
int main()
{
int sum=0;//牌数=13
int dian=0;//点数<=13
my(0,dian,sum);
printf("%d",num);
return 0;
}
[蓝桥杯2015初赛]奇妙的数字
题目描述 小明发现了一个奇妙的数字。它的平方和立方正好把0~9的10个数字每个用且只用了一次。你能猜出这个数字是多少吗?
输出
请输出该数字,不要输出任何多余的内容。
分析:
定义变量x2,表示数字的平方,x3表示数字的立方。
定义一个数组test[10],10元素分别代表(0-9)10个数出现的次数,然后取出x2和x3的每一位,用数组的10个元素来标识各个数出现的次数,每出现一次加1,最后再判断数组test的10个元素是不是1。如果为1,则说明满足题的要求,如果非1,则说明不满足题的要求。
没有时间和内存限制,就暴力循环吧,当然了代码效率越高肯定是越好的。
#include
#include
const int N = 1001;
bool solve(int a,int b)
{
int test[10];
memset(test,0,sizeof(test));
while(a)
{
test[a%10]++;
a/=10;
}
while(b)
{
test[b%10]++;
b/=10;
}
bool flag = true;
for(int i=0;i<10;i++)
{
if(test[i] != 1)
{
flag = false;
break;
}
}
return flag;
}
int main()
{
using std::cout;
int x2,x3;
for(int i=10;;i++)
{
int x2 = i*i;
int x3 = i*i*i;
if(solve(x2,x3))
{
cout<<i;
break;
}
}
return 0;
}
序列、向量、数组及其相互转化的观点
数组的遍历
通常的解法
for循环
= 还是 =
数组下标与线性表位序的换算公式
二维数组下标与矩阵位序的换算公式
无穷级数下标变化的总结与证明
初始化
遍历
查找
增加
删除
放缩法