*数据结构入门(郝斌)
数据结构和算法:
数据结构 = 个体的存储 + 个体的关系的存储
算法 = 对存储数据的操作
为何数据结构难学: 因为计算机内存是线性一维的, 而我们要处理的数据
都是比较复杂的, 那么怎么把这么多复杂的数据保存在计算机中来保存本
身就是一个难题, 而计算机在保存线性结构的时候比较好理解, 尤其是数
组和链表只不过是连续和离散的问题, 线性结构是我们学习的重点, 因为
线性算法比较成熟, 无论 C++还是 Java 中都有相关的工具例如 Arraylist.
Linkedlist,但是在 Java 中没有树和图, 因为非线性结构太复杂了, 他的
操作远远大于线性结构的操作。 即使 SUN 公司也没造出来。
衡量算法的标准:
1、时间复杂度
大概程序要执行的次数,而非执行的时间。
2、空间复杂度
算法执行过程中大概所占用的最大内存
3、难易程度(主要是应用方面看重)
4、健壮性(不能别人给一个非法的输入就挂掉)
指针 C 语言的灵魂
地址
地址就是内存单元的编号
从 0 开始的非负整数
范围:0–FFFFFFFF[0-4G-1](地址线是 32 位,刚好控制 2 的 32 次)
指针:
指针就是地址
地址就是指针
指针变量是存放内存单元地址的变量
指针的本质是一个操作受限的非负整数(不能加乘除,只能减)
求差的两个指针分别指向同一个数组的不同元素,通过计算求出两元素之间的距离
#include <stdio.h>
int main(void) {
int urn[5] = {100,200,300,400,500};
int *ptr1,*ptr2,n;
ptr1 = &urn[2];
ptr2 = &urn[4];
//指针减法
n = ptr2 - ptr1;
printf("ptr2 = %p , ptr1 = %p , n = %d", ptr2,ptr1,n);
return 0;
}
// 得到的结果是2,只表明指针减法得到的两元素之间的距离是每个数组元素的内存大小,而不是字节。使用得到的两个地址相减得到的是8(依字节编址情况下),说明整数型数组元素每个所占的内存大小是4B。
所有指针变量只占4个字节
#include<stdio.h>
int mian(){
double *p;
double x = 66.6;
p = &x; //x占八个字节,一个字节是8位,一个字节一个地址
double arr[3] = {1.1, 2.2,3.3};
double *q;
q = &arr[0];
printf("%p\n",q); //%p实际就是以16进制输出
q = &arr[1];
printf("%p\n",q);
// 结果中q为变量的地址,arr中一个元素差八个字节,其地址也就差8个大小。 22225C --- 22264
}
如何通过函数修改实参的值
1.该正常变量的实参
#include<stdio.h>
int main(void){
int i =10;
f(&i);
printf("%d\n",i);
}
void f(int *p){
*p = 99 ; //调用函数传递元素的地址,更改其实参值。
}
// 指针是唯一能够将调用函数中的局部变量传入到主函数中的方法
// 最终的输出结果为 99 通过指针将数据覆盖到实参的原位置,从而改变了实参值。
//改变指针变量的实参
#include<stdio.h>
// unsafe !!!
int main(void){
int i =10;
int *p = &i; //int *p ; p = &i;
printf("%d\n",p);
f(&p); //直接传数值到函数f()只能作为形参,不能改变实参值,只能传递地址元素。
printf("%d\n",p);
}
void f(int **q){ // 为了配合&p的数据类型,为指针的指针。
*q = (int *)0xFFFFFFFF; //调用函数传递元素的地址,更改其实参值。
// *q 理解为q的实参(q为指针的指针,因此q的实参其实也是指针(地址))
}
形参与实参
#include<stdio.h>
void A(int* p){//此处p为形参
int x = 3;
p = &x;
printf("在函数中p中的地址为%d\n\n",p);
}
int main(){
int e = 9;
int* p = &e; //此处p为实参
printf("main:p中的地址为: %d\n\n",p);
A(p);//此时传入的是地址值,只能作为形参;当传入地址时才能做实参。
printf("main:p中的地址为: %d\n\n",p);
return 0;
//main:p中的地址为: 1030875172
//在函数中p中的地址为1030875148
//main:p中的地址为: 1030875172
//假如在main方法中有一个指针变量p,它指向的内存地址为20000。现在将它传递到一个函数中,在这个函数里让它指向内存地址为20048。当函数执行完返回到main函数时,p所指向的地址还是20000,而不是20048。
结构体(C++中用类也能实现)
为什么会出现结构体
为了表示一些复杂的数据,而普通的基本类型变量无法满足要求
什么叫结构体
结构体是用户根据实际需要自己定义的复合数据类型
如何使用结构体
两种方式:
struct Student st = {1000, "zhangsan", 20}
struct Student * pst = &st;
\1. st.sid
\2. pst->sid
pst 所指向的结构体变量中的 sid 这个成员
#include<stdio.h>
#include <cstring>
struct Student
{
int sid;
char name[200];
int age;
};//创建类
int main(void){
struct Student st;//创建一个名为st的对象
f(&st);
g(st);
g2(&st);
// printf("%d %s %d\n",st.sid,st.name,st.age);
return 0;
}
// 结构体变量可以相互赋值,但实参一共占208个字节,浪费空间且耗时间。
// 传指针省空间 省时间。
void g(struct Student st)
{
printf("%d %s %d\n",st.sid,st.name,st.age);
}
void g2(struct Student *st)
{
printf("%d %s %d\n",st->sid,st->name,st->age);
}
void f (struct Student *pst)//想要传入实参改变实值必须传入地址
{
(*pst).sid = 99;
// *p.sid = p->sid (普通变量).sid == (指针变量)->sid
strcpy(pst->name,"zhangsan");
pst->age = 22;
}
注意事项:
结构体变量不能加减乘除,但可以相互赋值
普通结构体变量和结构体指针变量作为函数参数的传递
malloc 动态分配内存
#include<stdio.h>
#include<malloc.h>
int main(void ){
int a[5] = {4,10,2,8,6};
int len;
printf("请输入你需要分配的数组长度:");
scanf("%d",&len);
int *pArr = (int *)malloc(sizeof(int)*len);
//*pArr = 4; //动态分配的数组和普通数组有一样的用法
//pArr[1] = 10;
//printf("%d %d",*pArr,pArr[1]);
//我们可以当做一个普通数组来使用
for(int i = 0;i <len;i++)
scanf("%d",&pArr[i]);
for(int i = 0;i <len;i++)
printf("%d\n",*(pArr+i));
free(*pArr); //动态数组可以随时释放节省内存,释放(4字节)*len 大小的内存。
return 0;
}
跨函数使用内存
只有在使用动态内存,且动态内存没有free的前提下,函数调用时所占用的内存会保留。
#include<stdio.h>
struct Student{
int sid ;
int age;
};
struct Student *CreateStudent(void); //创建
void ShowStudent(struct Student *);
int main (void){
struct Student *ps; //创建一个动态数据类型的结构体
ps = CreateStudent(); //ps == p
ShowStudent(ps);
return 0;
}
void ShowStudent(struct Student *pst)
{
printf("%d %d",pst->sid,pst->age);
}
struct Student *CreateStudent(void){
struct Student *p = (struct Student *)malloc(sizeof(struct Student)); //创建了一个Student 结构体动态分配变量
p->sid = 99;
p->age = 20;
return p;
}
线性结构【把所有的结点用一根直线穿起来】
连续存储【数组】
1、什么叫做数组
元素类型相同,大小相等(数组传参,只要传进去首地址和长度就行)
2、数组的优缺点:
优点:
存取速度快
缺点:
事先必须知道数组的长度
插入删除元素很慢
空间通常是有限制的
需要大块连续的内存块
插入删除元素的效率很低
连续存储数组的算法演示
# include <stdio.h>
# include <malloc.h> //包含了malloc函数
# include <stdlib.h> //包含了exit函数
//定义了一个数据类型,该数据类型的名字叫做struct Arr, 该数据类型含有三个成员,分别是pBase, len, cnt
struct Arr
{
int * pBase; //存储的是数组第一个元素的地址
int len; //数组所能容纳的最大元素的个数
int cnt; //当前数组有效元素的个数
};
void init_arr(struct Arr * pArr, int length); //分号不能省
bool append_arr(struct Arr * pArr, int val); //追加
bool insert_arr(struct Arr * pArr, int pos, int val); // pos的值从1开始
bool delete_arr(struct Arr * pArr, int pos, int * pVal);
int get();
bool is_empty(struct Arr * pArr);
bool is_full(struct Arr * pArr);
void sort_arr(struct Arr * pArr);
void show_arr(struct Arr * pArr);
void inversion_arr(struct Arr * pArr);
int main(void)
{
struct Arr arr;
int val;
init_arr(&arr, 6);
show_arr(&arr);
append_arr(&arr, 1);
append_arr(&arr, 10);
append_arr(&arr, -3);
append_arr(&arr, 6);
append_arr(&arr, 88);
append_arr(&arr, 11);
if ( delete_arr(&arr, 4, &val) )
{
printf("删除成功!\n");
printf("您删除的元素是: %d\n", val);
}
else
{
printf("删除失败!\n");
}
/* append_arr(&arr, 2);
append_arr(&arr, 3);
append_arr(&arr, 4);
append_arr(&arr, 5);
insert_arr(&arr, -1, 99);
append_arr(&arr, 6);
append_arr(&arr, 7);
if ( append_arr(&arr, 8) )
{
printf("追加成功\n");
}
else
{
printf("追加失败!\n");
}
*/
show_arr(&arr);
inversion_arr(&arr);
printf("倒置之后的数组内容是:\n");
show_arr(&arr);
sort_arr(&arr);
show_arr(&arr);
//printf("%d\n", arr.len);
return 0;
}
void init_arr(struct Arr * pArr, int length)
{
pArr->pBase = (int *)malloc(sizeof(int) * length);
if (NULL == pArr->pBase)
{
printf("动态内存分配失败!\n");
exit(-1); //终止整个程序
}
else
{
pArr->len = length;
pArr->cnt = 0;
}
return;
}
bool is_empty(struct Arr * pArr)
{
if (0 == pArr->cnt)
return true;
else
return false;
}
bool is_full(struct Arr * pArr)
{
if (pArr->cnt == pArr->len)
return true;
else
return false;
}
void show_arr(struct Arr * pArr)
{
if ( is_empty(pArr) )
{
printf("数组为空!\n");
}
else
{
for (int i=0; i<pArr->cnt; ++i)
printf("%d ", pArr->pBase[i]); //int *
printf("\n");
}
}
bool append_arr(struct Arr * pArr, int val)
{
//满是返回false
if ( is_full(pArr) )
return false;
//不满时追加
pArr->pBase[pArr->cnt] = val; //pArr->pBase 为储存第一个元素的地址,也是一个动态数组。
(pArr->cnt)++; //cnt = 1 2...n;
//pArr->pBase[(pArr->cnt)++] = val;
return true;
}
bool insert_arr(struct Arr * pArr, int pos, int val)
{
int i;
if (is_full(pArr))
return false;
if (pos<1 || pos>pArr->cnt+1) //
return false;
for (i=pArr->cnt-1; i>=pos-1; --i)
{
pArr->pBase[i+1] = pArr->pBase[i];
}
pArr->pBase[pos-1] = val;
(pArr->cnt)++;
return true;
}
bool delete_arr(struct Arr * pArr, int pos, int * pVal)
{
int i;
if ( is_empty(pArr) )
return false;
if (pos<1 || pos>pArr->cnt)
return false;
*pVal = pArr->pBase[pos-1];
for (i=pos; i<pArr->cnt; ++i)
{
pArr->pBase[i-1] = pArr->pBase[i];
}
pArr->cnt--;
return true;
}
void inversion_arr(struct Arr * pArr)
{
int i = 0;
int j = pArr->cnt-1;
int t;
while (i < j)
{
t = pArr->pBase[i];
pArr->pBase[i] = pArr->pBase[j];
pArr->pBase[j] = t;
++i;
--j;
}
return;
}
void sort_arr(struct Arr * pArr)
{
int i, j, t;
for (i=0; i<pArr->cnt; ++i)
{
for (j=i+1; j<pArr->cnt; ++j)
{
if (pArr->pBase[i] > pArr->pBase[j])
{
t = pArr->pBase[i];
pArr->pBase[i] = pArr->pBase[j];
pArr->pBase[j] = t;
}
}
}
}
typedef_的用法
typedef int ZHANGSAN; //为已有的数据类型多取一个名字
typedef struct Arr
{
int * pBase; //存储的是数组第一个元素的地址
int len; //数组所能容纳的最大元素的个数
int cnt; //当前数组有效元素的个数
}ST;
struct Arr arr; // 等价于 ST arr;
typedef struct Arr
{
int * pBase; //存储的是数组第一个元素的地址
int len; //数组所能容纳的最大元素的个数
int cnt; //当前数组有效元素的个数
}* PST // PST 等价于 struct Arr
离散存储【链表】(我们搞底层的开发,类似于 SUN 公司的类)
链表的定义:
n 个节点离散分配
彼此通过指针相连
单向不分叉 ,每个节点只有一个前驱节点,每个节点只有一个后续节点
首节点没有前驱节点,尾节点没有后续节点。
专业术语:
首节点:
第一个有效节点
尾节点:
最后一个有效节点
头节点:
头结点的数据类型和首节点的类型一样
没有存放有效数据,最最前面的,是在
首节点之前的,主要是为了方便对链表
的操作。
头指针:(指向头)
指向头节点的指针变量
尾指针:
指向尾节点的指针(头结点有可能很大,占的内存可能大,假设我想造一个函数
输出所有链表的值,那你如果不用头指针类型做形参,那由于
不同链表的头节点不一样大小,这样就没办法找出形参)
确定一个链表需要几个参数:(或者说如果期望一个函数对链表进行操作
我们至少需要接收链表的那些信息???)
只需要一个参数:头指针,因为通过它我们可以推出链表的所有信息。
(链表的程序最好一定要自己敲出来)
// 每一个链表节点的数据类型
typedef struct Node
{
int data; //数据域
struct Node * pNext; //指针域 指向的是下一个链表节点整体(其数据类型和该节点的结构体变量一致)
}NODE,*PNODE; //NODE等价于struct node *PNODE等价于 struct node*
分类:
单链表
双链表:
每一个节点有两个指针域
循环链表
能通过任何一个节点找到其他所有的节点
非循环链表
java 中变成垃圾内存则会自动释放,但是 C 和 C++则不会,所以要
手动释放,否则会引起内存泄露。delete 等于 free)
非循环单链表插入与删除伪代码


//如何将p->pNext 赋值给r
//示例
// 每一个链表节点的数据类型
typedef struct Node
{
int data; //数据域
struct Node * pNext; //指针域 指向的是下一个链表节点整体(其数据类型和该节点的结构体变量一致)
}NODE,*PNODE; //NODE等价于struct node *PNODE等价于 struct node*
//
PNODE r = (PNODE)malloc(sizeof(NODE)); // r要存的是指针变量,因此要创建一个PNODE型的指针变量
r = p->pNext;
p->pNext = p->pNext->pNext;
free(r);
链表创建与链表遍历算法
// PNODE r = (PNODE)malloc(sizeof(NODE));
// 等价于 NODE* r = (NODE*)malloc(sizeof(NODE));
// 动态数组保存的变量是分配内存空间的地址
// 以上形式解释: 分配一个Node类型大小的内存空间,并把分配空间的首地址强制转换成pNode(指针)类型。
# include <stdio.h>
# include <malloc.h>
# include <stdlib.h>
typedef struct Node
{
int data; //数据域
struct Node * pNext; //指针域
}NODE, *PNODE; //NODE等价于struct Node PNODE等价于struct Node *
//函数声明
PNODE create_list(void); //创建链表
void traverse_list(PNODE pHead); //遍历链表
bool is_empty(PNODE pHead); //判断链表是否为空
int length_list(PNODE); //求链表长度
bool insert_list(PNODE pHead, int pos, int val); //在pHead所指向链表的第pos个节点的前面插入一个新的结点,该节点的值是val, 并且pos的值是从1开始
bool delete_list(PNODE pHead, int pos, int * pVal); //删除链表第pos个节点,并将删除的结点的值存入pVal所指向的变量中, 并且pos的值是从1开始
void sort_list(PNODE); //对链表进行排序
int main(void)
{
PNODE pHead = NULL; //等价于 struct Node * pHead = NULL;
int val;
pHead = create_list(); //create_list()功能:创建一个非循环单链表,并将该链表的头结点的地址付给pHead
traverse_list(pHead);
//insert_list(pHead, -4, 33);
if ( delete_list(pHead, 4, &val) )
{
printf("删除成功,您删除的元素是: %d\n", val);
}
else
{
printf("删除失败!您删除的元素不存在!\n");
}
traverse_list(pHead);
//int len = length_list(pHead);
//printf("链表的长度是%d\n", len);
//sort_list(pHead);
//traverse_list(pHead);
/* if ( is_empty(pHead) )
printf("链表为空!\n");
else
printf("链表不空!\n");
*/
return 0;
}
PNODE create_list(void)
{
int len; //用来存放有效节点的个数
int i;
int val; //用来临时存放用户输入的结点的值
//分配了一个不存放有效数据的头结点
PNODE pHead = (PNODE)malloc(sizeof(NODE));
if (NULL == pHead)
{
printf("分配失败, 程序终止!\n");
exit(-1);
}
PNODE pTail = pHead;
pTail->pNext = NULL;
printf("请输入您需要生成的链表节点的个数: len = ");
scanf("%d", &len);
for (i=0; i<len; ++i)
{
printf("请输入第%d个节点的值: ", i+1);
scanf("%d", &val);
PNODE pNew = (PNODE)malloc(sizeof(NODE));
if (NULL == pNew)
{
printf("分配失败, 程序终止!\n");
exit(-1);
}
pNew->data = val;
pTail->pNext = pNew;
pNew->pNext = NULL;
pTail = pNew;
}
return pHead;
}
void traverse_list(PNODE pHead)
{
PNODE p = pHead->pNext;
while (NULL != p)
{
printf("%d ", p->data);
p = p->pNext;
}
printf("\n");
return;
}
bool is_empty(PNODE pHead)
{
if (NULL == pHead->pNext)
return true;
else
return false;
}
int length_list(PNODE pHead)
{
PNODE p = pHead->pNext;
int len = 0;
while (NULL != p)
{
++len;
p = p->pNext;
}
return len;
}
void sort_list(PNODE pHead)
{
int i, j, t;
int len = length_list(pHead);
PNODE p, q;
for (i=0,p=pHead->pNext; i<len-1; ++i,p=p->pNext)
{
for (j=i+1,q=p->pNext; j<len; ++j,q=q->pNext)
{
if (p->data > q->data) //类似于数组中的: a[i] > a[j]
{
t = p->data;//类似于数组中的: t = a[i];
p->data = q->data; //类似于数组中的: a[i] = a[j];
q->data = t; //类似于数组中的: a[j] = t;
}
}
}
return;
}
//在pHead所指向链表的第pos个节点的前面插入一个新的结点,该节点的值是val, 并且pos的值是从1开始
bool insert_list(PNODE pHead, int pos, int val)
{
int i = 0;
PNODE p = pHead;
while (NULL!=p && i<pos-1)
{
p = p->pNext;
++i;
}
if (i>pos-1 || NULL==p)
return false;
//如果程序能执行到这一行说明p已经指向了第pos-1个结点,但第pos-1个节点是否存在无所谓
//分配新的结点
PNODE pNew = (PNODE)malloc(sizeof(NODE));
if (NULL == pNew)
{
printf("动态分配内存失败!\n");
exit(-1);
}
pNew->data = val;
//将新的结点存入p节点的后面
PNODE q = p->pNext;
p->pNext = pNew;
pNew->pNext = q;
return true;
}
bool delete_list(PNODE pHead, int pos, int * pVal)
{
int i = 0;
PNODE p = pHead;
while (NULL!=p->pNext && i<pos-1)
{
p = p->pNext;
++i;
}
if (i>pos-1 || NULL==p->pNext)
return false;
//如果程序能执行到这一行说明p已经指向了第pos-1个结点,并且第pos个节点是存在的
PNODE q = p->pNext; //q指向待删除的结点
*pVal = q->data;
//删除p节点后面的结点
p->pNext = p->pNext->pNext;
//释放q所指向的节点所占的内存
free(q);
q = NULL;
return true;
}
泛型 : 不同的存储方式 ,执行的操作是一致的
数组存储方式和链表存储方式进行冒泡排序算法
// 冒泡排序 数组形式存贮
void sort_list(PHODE pHead)//从小到大
{
int i,j,k;
for(i = 0;i<len-1;i++)
{
for(j = j+1;j<len;j++){
if(a[i]>a[j])
{
t = a[i];
a[i] = a[j];
a[j] = t;
}
}
}
}
void sort_list(PNODE pHead)
{
int i, j, t;
int len = length_list(pHead);
PNODE p, q;
for (i=0,p=pHead->pNext; i<len-1; ++i,p=p->pNext)
{
for (j=i+1,q=p->pNext; j<len; ++j,q=q->pNext)
{
if (p->data > q->data) //类似于数组中的: a[i] > a[j]
{
t = p->data;//类似于数组中的: t = a[i];
p->data = q->data; //类似于数组中的: a[i] = a[j];
q->data = t; //类似于数组中的: a[j] = t;
}
}
}
return;
}
insert and delete
bool insert_list(PNODE pHead, int pos, int val)
{
//需要判断是否为空以及需要遍历到pos前面的一个节点的位置
int i=0;
PNODE p = pHead; // p = pHead ->pNext 为第一个有效元素的位置 此时i=1;
while(p!=NULL && i < pos-1) // 需要在第pos-1个节点的位置停下,此时i= pos -1 //i不能达到pos值,因此在i=pos-1时就不在允许进一步操作。
{
p = p->pNext;
i++;
}
if (i>pos-1 || NULL==p->pNext)
return false;
PNODE pNew = (PNODE)malloc(sizeof(NODE));
if (NULL == pNew)
{
printf("动态分配内存失败!\n");
exit(-1);
}
pNew->data = val;
//将新的结点存入p节点的后面
pNew->pNext = p->pNext;
p->pNext = pNew;
return true;
}
bool delete_list(PNODE pHead, int pos, int * pVal)
{
while(p!=NULL && i < pos-1) // 需要在第pos-1个节点的位置停下,此时i= pos -1 //i不能达到pos值,因此在i=pos-1时就不在允许进一步操作。
{
p = p->pNext;
i++;
}
if (i>pos-1 || NULL==p->pNext)
return false;
//如果程序能执行到这一行说明p已经指向了第pos-1个结点,并且第pos个节点是存在的
PNODE q = p->pNext; //q指向待删除的结点
*pVal = q->data;
//删除p节点后面的结点
p->pNext = p->pNext->pNext;
//释放q所指向的节点所占的内存
free(q);
q = NULL;
return true;
}
栈
栈的定义
定义:一种可以实现“先进后出”数据存储结构
类似于储物箱,后放的在表层,先取。
凡是静态的变量()或者局部变量都是在栈里分配的内存(以压栈和出栈的方式)
凡是动态变量都是在堆里分配得到的(使用堆排序的方式)
栈的分类
静态栈
动态栈
栈的算法
静态栈:
// 入栈
void Push(Stack PtrS,ElementType item)
{
if (PtrS->Top == MaxSize-1){
printf("堆栈满"); return;
}else{
//栈空时 ptrS->Top = 0;
PtrS->Data[++(ptrS->Top)]=item;//(ptrS->Top)作用相当于指针,++(ptrS->Top)代表先加后用
return;
}
// 出栈
ElementType Pop(Stack PtrS)
{
if(PtrS->Top==-1){
printf("堆栈空");
return ERROR; //ERROR 是 ElementType 的特殊值,标志错误。
}else
return(PtrS->Data[(PtrS->Top)--]);//先用后减
}
动态栈:

# include <stdio.h>
# include <malloc.h>
# include <stdlib.h>
typedef struct Node
{
int data;
struct Node * pNext;
}NODE, * PNODE;
typedef struct Stack
{
PNODE pTop;
PNODE pBottom;
}STACK, * PSTACK; //PSTACK 等价于 struct STACK *
void init(PSTACK);
void push(PSTACK, int );
void traverse(PSTACK);
bool pop(PSTACK, int *);
void clear(PSTACK pS);
int main(void)
{
STACK S; //STACK 等价于 struct Stack
int val;
init(&S); //目的是造出一个空栈
push(&S, 1); //压栈
push(&S, 2);
push(&S, 3);
push(&S, 4);
push(&S, 5);
push(&S, 6);
traverse(&S); //遍历输出
clear(&S);
//traverse(&S); //遍历输出
if ( pop(&S, &val) )
{
printf("出栈成功,出栈的元素是%d\n", val);
}
else
{
printf("出栈失败!\n");
}
traverse(&S); //遍历输出
return 0;
}
void init(PSTACK pS)
{
pS->pTop = (PNODE)malloc(sizeof(NODE));
if (NULL == pS->pTop)
{
printf("动态内存分配失败!\n");
exit(-1);
}
else
{
pS->pBottom = pS->pTop;
pS->pTop->pNext = NULL; //pS->Bottom->pNext = NULL;
}
}
void push(PSTACK pS, int val)
{
PNODE pNew = (PNODE)malloc(sizeof(NODE));
pNew->data = val;
pNew->pNext = pS->pTop; //pS->Top不能改成pS->Bottom
pS->pTop = pNew;
return;
}
void traverse(PSTACK pS)
{
PNODE p = pS->pTop;
while (p != pS->pBottom)
{
printf("%d ", p->data);
p = p->pNext;
}
printf("\n");
return;
}
bool empty(PSTACK pS)
{
if (pS->pTop == pS->pBottom)
return true;
else
return false;
}
//把pS所指向的栈出栈一次,并把出栈的元素存入pVal形参所指向的变量中,如果出栈失败,返回false,否则返回true
bool pop(PSTACK pS, int * pVal)
{
if ( empty(pS) ) //pS本身存放的就是S的地址
{
return false;
}
else
{
PNODE r = pS->pTop;
*pVal = r->data;
pS->pTop = r->pNext;
free(r);
r = NULL;
return true;
}
}
//clear清空
void clear(PSTACK pS)
{
if (empty(pS))
{
return;
}
else
{
PNODE p = pS->pTop;
PNODE q = NULL;
while (p != pS->pBottom)
{
q = p->pNext;
free(p);
p = q;
}
pS->pTop = pS->pBottom;
}
}
栈的应用
1.函数调用(所有的函数调用都是以这种方式实现的)
2.中断
3.表达式求值
4.内存分配
5.缓冲处理
6.迷宫
队列
定义:一种可以实现“先进先出”的存储结构
类似于买票排队
分类:
链式队列: 用链表实现
静态队列: 用数组实现
静态队列通常都必须是循环队列, 为了减少
内存浪费。




如何判断队列中有多少个元素
判断队列中元素个数的依据是指针变量rear和front之间的差,也即是两者对应空间的距离。问题在于n个元素时,两者之间距离最多有n种可能性,而队列的状态却有n+1种可能性。(这意味着存在矛盾,实际上无法区分队列是“满”还是“空”。)
解决方案:1.仅使用n-1个队列空间
2.使用额外标记:Size或者tag域(元素加1,size+1)
算法实现
// 链式队列
# include <iostream>
using namespace std;
typedef struct node
{
int data;
struct node *pNext;
}NODE, *PNODE;
class Queue
{
public:
Queue()
{
this->pHead = this->pTail = new NODE;
this->pHead->pNext = NULL;
}
void InQueue(int val)
{
PNODE pNew = new NODE;
pNew->data = val;
pNew->pNext = NULL;
pTail->pNext = pNew; //将pNew挂到队列尾部
pTail = pNew; //注意是尾指针上移
return;
}
bool Empty() const
{
if (this->pHead == pTail)
return true;
else
return false;
}
int OutQueue()
{
if (Empty())
{
cout <<"队列为空,无法出队!" << endl;
}
else
{
PNODE pTemp = pHead->pNext; //pHead不是要删除的队首元素,pHead->pNext所指向的元素才是要删除的元素,
pHead->pNext = pTemp->pNext;
int val = pTemp->data;
delete pTemp;
if (NULL == pHead->pNext) //如果队列为空
{
pTail = pHead; //尾指针也指向无用的头结点
}
return val;
}
}
//遍历队列
void Travers(void) const
{
PNODE pTemp = pHead->pNext;
while (pTemp != NULL)
{
cout << pTemp->data << " ";
pTemp = pTemp->pNext;
}
cout << endl;
}
void Clear()
{
while (! this->Empty())
{
OutQueue();
}
}
~Queue()
{
this->Clear();
delete pHead;
}
private:
PNODE pHead, pTail; //pHead指向无用的头结点 pHead->pNext才是指向队首元素, pTail指向队尾元素
};
int main(void)
{
Queue Q;
for (int i=0; i<5; ++i)
Q.InQueue(i+1);
Q.Travers();
Q.OutQueue();
Q.OutQueue();
Q.Travers();
Q.Clear();
Q.OutQueue();
return 0;
}
// 循环队列
# include <stdio.h>
# include <malloc.h>
typedef struct Queue
{
int * pBase;
int front;
int rear;
}QUEUE;
void init(QUEUE *);
bool en_queue(QUEUE *, int val); //入队
void traverse_queue(QUEUE *);
bool full_queue(QUEUE *);
bool out_queue(QUEUE *, int *);
bool emput_queue(QUEUE *);
int main(void)
{
QUEUE Q;
int val;
init(&Q);
en_queue(&Q, 1);
en_queue(&Q, 2);
en_queue(&Q, 3);
en_queue(&Q, 4);
en_queue(&Q, 5);
en_queue(&Q, 6);
en_queue(&Q, 7);
en_queue(&Q, 8);
traverse_queue(&Q);
if ( out_queue(&Q, &val) )
{
printf("出队成功,队列出队的元素是: %d\n", val);
}
else
{
printf("出队失败!\n");
}
traverse_queue(&Q);
return 0;
}
void init(QUEUE *pQ)
{
pQ->pBase = (int *)malloc(sizeof(int) * 6);
pQ->front = 0;
pQ->rear = 0;
}
bool full_queue(QUEUE * pQ)
{
if ( (pQ->rear + 1) % 6 == pQ->front )
return true;
else
return false;
}
bool en_queue(QUEUE * pQ, int val)
{
if ( full_queue(pQ) )
{
return false;
}
else
{
pQ->pBase[pQ->rear] = val;
pQ->rear = (pQ->rear+1) % 6;
return true;
}
}
void traverse_queue(QUEUE * pQ)
{
int i = pQ->front;
while (i != pQ->rear)
{
printf("%d ", pQ->pBase[i]);
i = (i+1) % 6;
}
printf("\n");
return;
}
bool emput_queue(QUEUE * pQ)
{
if ( pQ->front == pQ->rear )
return true;
else
return false;
}
bool out_queue(QUEUE * pQ, int * pVal)
{
if ( emput_queue(pQ) )
{
return false;
}
else
{
*pVal = pQ->pBase[pQ->front];
pQ->front = (pQ->front+1) % 6;
return true;
}
}
递归
定义: 一个函数直接或者间接调用自己
// 阶乘 使用循环实现
# include <stdio.h>
int main(void)
{
int val;
int i, mult=1;
printf("请输入一个数字: ");
printf("val = ");
scanf("%d", &val);
for (i=1; i<=val; ++i)
mult = mult * i;
printf("%d的阶乘是:%d\n", val, mult);
return 0;
}
// 阶乘 使用递归实现
# include <stdio.h>
//假定n的值是1或大于1的值
long f(long n)
{
if (1 == n)
return 1;
else
return f(n-1) * n;
}
int main(void)
{
printf("%ld\n", f(100));
return 0;
}
一个函数为什么能调用自己

递归必须满足的三个条件
1.递归必须有一个明确的终止条件
2.该函数所处理的数据规模必须在递减
3.这个转化必须是可解的
循环可以转化为递归,一些新的问题也可以转化为递归
递归和循环之间的优缺点:
递归: 易于理解 速度慢 存储空间比较大
循环: 不易理解 速度快 存储空间小
汉诺塔




# include <stdio.h>
void hannuota(int n, char A, char B, char C)
{
/*
如果是1个盘子
直接将A柱子上的盘子从A移到C
否则
先将A柱子上的n-1个盘子借助C移到B
直接将A柱子上的盘子从A移到C
最后将B柱子上的n-1个盘子借助A移到C
*/
if (1 == n)
{
printf("将编号为%d的盘子直接从%c柱子移到%c柱子\n", n, A, C);
}
else
{
hannuota(n-1, A, C, B);
printf("将编号为%d的盘子直接从%c柱子移到%c柱子\n", n, A, C);
hannuota(n-1, B, A, C);
}
}
int main(void)
{
char ch1 = 'A';
char ch2 = 'B';
char ch3 = 'C';
int n;
printf("请输入要移动盘子的个数: ");
scanf("%d", &n);
hannuota(n, 'A', 'B', 'C');
return 0;
}
递归的应用
1.树和森林就是以递归的方式定义的
2.树和图的很多算法都是以递归来实现的
3.很多数学公式就是以递归的方式定义的
图
排序
# include <stdio.h>
int FindPos(int * a, int low, int high);
void QuickSort(int * a, int low, int high);
int main(void)
{
int a[6] = {-2, 1, 0, -985, 4, -93};
int i;
QuickSort(a, 0, 5); //第二个参数表示第一个元素的下标 第三个参数表示最后一个元素的下标
for (i=0; i<6; ++i)
printf("%d ", a[i]);
printf("\n");
return 0;
}
void QuickSort(int * a, int low, int high)
{
int pos;
if (low < high)
{
pos = FindPos(a, low, high);
QuickSort(a, low, pos-1);
QuickSort(a, pos+1, high);
}
}
int FindPos(int * a, int low, int high)
{
int val = a[low];
while (low < high)
{
while (low<high && a[high]>=val)
--high;
a[low] = a[high];
while (low<high && a[low]<=val)
++low;
a[high] = a[low];
}//终止while循环之后low和high一定是相等的
a[low] = val;
return high; //high可以改为low, 但不能改为val 也不能改为a[low] 也不能改为a[high]
}
dbfxNp-1721151786006)]
[外链图片转存中…(img-v6WEx2e1-1721151786007)]
[外链图片转存中…(img-KehD2hCH-1721151786007)]
[外链图片转存中…(img-8NmRLTTs-1721151786008)]
# include <stdio.h>
void hannuota(int n, char A, char B, char C)
{
/*
如果是1个盘子
直接将A柱子上的盘子从A移到C
否则
先将A柱子上的n-1个盘子借助C移到B
直接将A柱子上的盘子从A移到C
最后将B柱子上的n-1个盘子借助A移到C
*/
if (1 == n)
{
printf("将编号为%d的盘子直接从%c柱子移到%c柱子\n", n, A, C);
}
else
{
hannuota(n-1, A, C, B);
printf("将编号为%d的盘子直接从%c柱子移到%c柱子\n", n, A, C);
hannuota(n-1, B, A, C);
}
}
int main(void)
{
char ch1 = 'A';
char ch2 = 'B';
char ch3 = 'C';
int n;
printf("请输入要移动盘子的个数: ");
scanf("%d", &n);
hannuota(n, 'A', 'B', 'C');
return 0;
}
递归的应用
1.树和森林就是以递归的方式定义的
2.树和图的很多算法都是以递归来实现的
3.很多数学公式就是以递归的方式定义的
图
排序
# include <stdio.h>
int FindPos(int * a, int low, int high);
void QuickSort(int * a, int low, int high);
int main(void)
{
int a[6] = {-2, 1, 0, -985, 4, -93};
int i;
QuickSort(a, 0, 5); //第二个参数表示第一个元素的下标 第三个参数表示最后一个元素的下标
for (i=0; i<6; ++i)
printf("%d ", a[i]);
printf("\n");
return 0;
}
void QuickSort(int * a, int low, int high)
{
int pos;
if (low < high)
{
pos = FindPos(a, low, high);
QuickSort(a, low, pos-1);
QuickSort(a, pos+1, high);
}
}
int FindPos(int * a, int low, int high)
{
int val = a[low];
while (low < high)
{
while (low<high && a[high]>=val)
--high;
a[low] = a[high];
while (low<high && a[low]<=val)
++low;
a[high] = a[low];
}//终止while循环之后low和high一定是相等的
a[low] = val;
return high; //high可以改为low, 但不能改为val 也不能改为a[low] 也不能改为a[high]
}
352

被折叠的 条评论
为什么被折叠?



