时间:2015-4-30 11:30
加油
————————————————————————————————————————————————————————
——数据结构概述
一、定义
我们如何把现实中大量而复杂的问题以特定的数据类型(个体)和特定的存储结构(个体之间的关系)保存到主存储器(内存)中,
以及在此基础上为实现某个功能(比如查找某个元素,删除某个元素,对所有元素进行排序)而执行的相应操作,这个相应的操作也
叫做算法(算法就是操作)。
数据结构 = 个体 + 个体的关系
算法 = 对存储数据的操作(存储方式不同,算法也不同)
二、算法
解题的方法和步骤
衡量算法的标准
1、时间复杂度
程序大概执行的次数,而非执行的时间。
执行的时间依赖于硬件,而硬件在不断的发展变化,所以不能把时间当做一个标准。
2、空间复杂度
算法执行过程中大概所占用的最大的内存。
3、难易程度
算法不但要优,而且要容易被理解。
4、健壮性
保证有错误输入时程序不会出错。
三、数据结构的地位
数据结构是软件中最核心的课程。
程序 = 数据的存储 + 数据的操作 + 可以被计算机执行的语言
——预备知识
一、指针
1、指针的重要性:
指针是C语言的灵魂
2、定义:
地址
内存单元的编号。
从0开始的非负整数。
范围:0 ~ FFFFFFFFF(0 ~ 4G-1),针对于32位CPU。
指针:
指针就是地址,地址就是指针。
指针变量是存放内存单元地址的变量。
指针的本质是一个操作受限的非负整数。
3、指针和数组
指针和一维数组
数组名:
一位数组名是一个指针常量。
它存放的是一维数组第一个元素的地址。
它的值不能被改变。
一位数组名指向的是数组的第一个元素
下标和指针的关系
a[i] == *(a+i)(偏移地址)
假设指针变量的名字为a,则a+i的值是a+i*(a所指向的变量所占的字节数)
也可以写成:i[a]
指针变量的运算
指针变量不能相加,不能相乘,不能相除。
如果两个指针变量属于同一数组,则可以相减。
指针变量可以加减一个整数,前提是最终结果不能超过指针变量所指向内存的地址范围。
二、结构体
1、为什么会出现结构体
为了表示一些复杂的数据,而普通的基本类型变量无法满足需求。
2、什么是结构体
结构体是用户根据实际需求自己定义的复合数据类型。
3、如何使用结构体
两种方式:
* struct Student st = {100,"zhangsan",20};
st.sid = 20;
* struct Student * pst = &st;
pst->sid = 20;//表示pst所指向的结构体变量中的sid成员
4、注意事项
结构体变量不能加减乘除,但是可以相互赋值。
普通结构体变量和结构体指针变量作为函数传参的问题。
代码如下:
-----------------------------------------------------------------------------------------------------------
# include<stdio.h>
# include<string.h>
struct Student
{
int sid;
char name[20];
int age;
};
//修改值
void fun(struct Student * pst)
{
pst->age = 20;
strcpy(pst->name, "lisi");
pst->sid = 100;
}
//输出值
void show(struct Student * pst)
{
printf("%d %s %d
", pst->sid, pst->name, pst->age);
}
int main(void)
{
struct Student st;//已经为st分配了内存空间,只不过内存中没有值。例如 int i;i已经有内存空间。
fun(&st);
//printf("%d %s %d
",st.sid,st.name,st.age);
show(&st);
return 0;
}
-----------------------------------------------------------------------------------------------------------
三、动态内存的分配和释放
# include<stdio.h>
# include<stdlib.h>
int main(void)
{
int arr[5] = { 1,2,3,4,5 };
int len;
printf("请输入您需要分配的数组的长度:len = ");
scanf("%d", &len);
int *pArr = (int *)malloc(sizeof(int) * len);
*pArr = 1;//类似于a[0] = 1
pArr[1] = 2;//类似于a[1] = 2
printf("%d %d
",*pArr,pArr[1]);
//可以把pArr当做一个普通数组来使用
for (int i = 0; i < len; i++)
{
scanf("%d", &pArr[i]);
}
//输出
for (int i = 0; i < len; i++)
{
printf("%d ", *(pArr + i));
}
free(pArr);//将pArr所指向的动态分配的内存释放
return 0;
}
——模块一:线性结构【把所有的结点用一根线穿起来】
——连续存储【数组】
1、什么叫数组
元素类型相同,大小相等
2、数组的优缺点
优点:
存取元素的效率非常高
缺点:
事先必须知道数组的长度
插入删除元素的效率低
需要大块连续的内存块
=================================================================
——数组的操作:
# include<stdio.h>
# include<stdlib.h>
# include<string.h>
//定义了一个数据类型,该数据类型的名字叫做struct Arr
//该数据类型含有三个成员,分别是pBase,len,cnt
struct Arr
{
int * pBase;//存储的是数组第一个元素的地址
int len;//数组所能容纳的最大元素的个数
int cnt;//当前数组有效元素的个数
//int increment;//自动增长因子
};
//定义方法
void init_arr(struct Arr *,int);//初始化数组
bool append_arr(struct Arr *,int);//追加元素
bool insert_arr(struct Arr *,int value,int index);//插入元素
bool delete_arr(struct Arr *, int index);//删除元素
int get_arr(struct Arr * ,int index);//根据脚标获取元素
bool isEmpty_arr(struct Arr *);//判断是否为空
bool isFull_arr(struct Arr *);//判断数组是否已满
void sort_arr(struct Arr *);//数组排序
void inversion(struct Arr *);//数组倒置
void show_arr(struct Arr *);//打印数组
int main(void)
{
struct Arr arr;//定义结构体变量
int length = 0;//数组长度
int number = 0;//追加值变量
int index = 0;//数组脚标
int ch = 0;
while (true)
{
printf("请输入您的选项
");
printf("1:初始化数组
");
printf("2:删除元素
");
printf("3:追加元素
");
printf("4:插入元素
");
printf("5:删除元素
");
printf("6:获取元素
");
printf("7:数组排序
");
printf("8:数组倒序
");
scanf("%d",&ch);
switch (ch)
{
case 1:
printf("请输入数组长度:");
scanf("%d", &length);
init_arr(&arr, length);
printf("打印初始化后的数组:
");
show_arr(&arr);
break;
case 2:
//删除元素
printf("请输入要删除元素的脚标:");
scanf("%d", &index);
delete_arr(&arr, index);
printf("打印删除后的数组:
");
show_arr(&arr);
break;
case 3:
//追加元素
printf("请输入要追加的元素:");
scanf("%d", &number);
append_arr(&arr, number);
printf("打印追加后的数组:
");
show_arr(&arr);
break;
case 4:
//插入元素
printf("请输入要插入的元素:");
scanf("%d", &number);
printf("请输入要插入的位置:");
scanf("%d", &index);
insert_arr(&arr, number, index);
printf("打印插入元素后的数组:
");
show_arr(&arr);
break;
case 5:
//删除元素
printf("请输入要删除元素的脚标:");
scanf("%d", &index);
delete_arr(&arr, index);
printf("打印删除后的数组:
");
show_arr(&arr);
break;
case 6:
//根据脚标获取元素
printf("请输入要获取元素的脚标:");
scanf("%d", &index);
printf("打印获取的元素:%d
", get_arr(&arr, index));
break;
case 7:
//数组排序
printf("打印排序后数组:
");
sort_arr(&arr);
show_arr(&arr);
break;
case 8:
//数组倒序
printf("打印倒序后的数组:
");
inversion(&arr);
show_arr(&arr);
break;
default:
printf("输入不正确,请重新输入:");
}
}
return 0;
}
//初始化数组
void init_arr(struct Arr * pArr,int length)
{
pArr->pBase = (int*)malloc(sizeof(int) * length);
if (NULL == pArr->pBase)
{
printf("动态内存分配失败!");
exit(-1);
}
else
{
pArr->len = length;
pArr->cnt = 0;
printf("请输入数组元素:
");
for (int i = 0; i < length; i++)
{
int number;
scanf("%d",&number);
pArr->pBase[i] = number;
pArr->cnt++;
}
}
return;
}
//打印数组
void show_arr(struct Arr * pArr)
{
if (isEmpty_arr(pArr))
{
printf("数组为空
");
}
else
{
for (int i = 0; i < pArr->cnt; i++)
{
printf("%d",pArr->pBase[i]);
}
printf("
");
}
}
//判断数组是否为空
bool isEmpty_arr(struct Arr * pArr)
{
if (0 == pArr->cnt)
return true;
else
return false;
}
//判断数组是否已满
bool isFull_arr(struct Arr * pArr)
{
if (pArr->cnt == pArr->len)
return true;
return false;
}
//追加元素
bool append_arr(struct Arr * pArr, int value)
{
//如果数组已满返回false
if (isFull_arr(pArr))
{
printf("数组已满
");
return false;
}
//如果不满就追加
pArr->pBase[pArr->cnt] = value;
pArr->cnt++;
return true;
}
//插入元素
bool insert_arr(struct Arr * pArr, int value, int index)
{
if (isEmpty_arr(pArr))
{
printf("数组为空
");
return false;
}
//如果数组已满返回false
if (isFull_arr(pArr))
{
printf("数组已满
");
return false;
}
int i = pArr->cnt;
for (; i > index; i--)
{
pArr->pBase[i] = pArr->pBase[i - 1];
}
pArr->pBase[i] = value;
pArr->cnt++;
return true;
}
//删除元素
bool delete_arr(struct Arr * pArr, int index)
{
if (isEmpty_arr(pArr))
{
printf("数组为空
");
return false;
}
if (index > pArr->cnt)
{
printf("脚标不存在
");
return false;
}
int i = index;
for (;i < pArr->cnt; i++)
{
pArr->pBase[i] = pArr->pBase[i + 1];
}
pArr->pBase[i] = 0;
pArr->cnt--;
}
//根据脚标获取元素
int get_arr(struct Arr * pArr, int index)
{
if (isEmpty_arr(pArr))
{
printf("数组为空
");
return false;
}
if (index > pArr->cnt)
{
printf("脚标不存在
");
return false;
}
return pArr->pBase[index];
}
//数组排序
void sort_arr(struct Arr * pArr)
{
if (isEmpty_arr(pArr))
{
printf("数组为空");
return;
}
for (int i = 0; i < pArr->cnt;i++)
{
for (int j = i; j < pArr->cnt;j++)
{
if (pArr->pBase[i] > pArr->pBase[j])
{
int temp = pArr->pBase[i];
pArr->pBase[i] = pArr->pBase[j];
pArr->pBase[j] = temp;
}
}
}
}
//数组倒置
void inversion_arr(struct Arr * pArr)
{
int i, j, t;
i = 0;
j = pArr->cnt - 1;
while (i < j)
{
t = pArr->pBase[i];
pArr->pBase[i] = pArr->pBase[j];
pArr->pBase[j] = t;
i++;
j--;
}
}
=================================================================
——typedef的用法
# include<stdio.h>
typedef int aa;//aa等价于int,可以使用aa来定义int类型变量
typedef struct Student
{
int sid;
char name[20];
char sex;
}ST,* PST;
//ST代表结构体,等价于struct Student 类型
//PST代表指针,等价于struct Student * 类型
int main(void)
{
aa a = 1;
printf("%d
",a);
ST st = { 10,"111",'男' };
printf("%d %s %c
", st.sid, st.name, st.sex);
ST * pst = &st;
printf("%d %s %c
", pst->sid, pst->name, pst->sex);
PST pst2 = &st;
printf("%d %s %c
", pst2->sid, pst2->name, pst2->sex);
return 0;
}
=================================================================
——离散存储【链表】
定义:
N个结点离散分配
彼此通过指针相连
每个结点只有一个前驱结点,每个结点只有一个后继结点
首结点无前驱,尾结点无后继
术语:
首结点:
第一个存放有效数据的结点
尾结点:
最后一个存放有效数据的结点
头结点:
第一个存放有效数据结点之前的结点
头结点并不存放有效数据
加头结点的目的主要是为了方便对链表的操作
头结点的数据类型和首节点类型一样
→ 头指针:
| 指向头结点的指针变量
| 尾指针:
| 指向尾结点的指针变量
|
| __如果希望通过一个函数来对链表进行处理,我们至少需要接收链表的哪些参数?
只需要一个参数:头指针。因为我们通过头指针可以推算出链表的其它所有参数
数据域:
存储数据元素信息的区域
指针域:
存储直接后继存储位置的区域
分类:
单链表:
双链表:
每一个结点有两个指针域
循环链表:
能通过任何一个结点找到其它所有结点
非循环链表
算法:
遍历
查找
清空
销毁
求长度
排序
删除结点:
p->pNext = p->pNext->pNext(错,这样写会导致内存泄漏)
free(p->pNext)(错,会导致链表断裂)
√ r = p->pNext; //指向p后面的结点
p->pNext = r->pNext;
free(r);
插入结点:把q所指向的结点插到p所指向的结点的后面
1、 r = p->pNext; //r指向p后面的那个结点
p->pNext = q; //q是指针变量
q->pNext = r;
2、q->pNext = p->pNext; //先接后面结点的地址
p->pNext = q;
算法:
狭义的算法是与数据的存储方式密切相关。
广义的算法是与数据的存储方式无关。
泛型:
不同的存储方式,执行的操作是一样的。
链表的优缺点:
优点:
空间没有限制
插入删除元素很快
缺点:
存取元素速度慢
=================================================================
——链表的算法操作
# include<stdio.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 pHead);//求链表长度
bool insert_list(PNODE pHead, int index, int value);//插入元素
bool delete_list(PNODE pHead, int index, int * value);//删除元素
void sort_list(PNODE pHead);//链表排序
int main(void)
{
PNODE pHead = NULL;//等价于 struct Node * pHead = NULL;
int select = 0;
int pos = 0;
int value = 0;
while (true)
{
printf("请选择操作:
");
printf("1、创建链表:
");
printf("2、遍历链表:
");
printf("3、输出链表长度:
");
printf("4、链表排序:
");
printf("5、插入元素:
");
printf("6、删除元素:
");
scanf("%d",&select);
switch (select)
{
case 1:
pHead = create_list();//创建一个非循环单链表并将该链表的头结点返回给pHead
break;
case 2:
traverse_list(pHead);
break;
case 3:
printf("链表长度为:%d
",length_list(pHead));
break;
case 4:
sort_list(pHead);
break;
case 5:
printf("请输入要插入的值:");
scanf("%d", &value);
printf("请输入要插入的位置:");
scanf("%d", &pos);
insert_list(pHead, pos, value);
break;
case 6:
printf("请输入要删除的结点脚标:");
scanf("%d", &pos);
delete_list(pHead, pos,&value);
printf("您删除的元素是:%d
",value);
break;
}
printf("
");
}
return 0;
}
//创建非循环单链表
PNODE create_list(void)
{
int len = 0;//用来存放有效结点的个数
int i = 0;//循环变量
int value = 0;//用来临时存放结点的值
//创建了一个不存放有效数据的头结点
PNODE pHead = (PNODE)malloc(sizeof(NODE));
if (NULL == pHead)//判断创建临时头结点是否成功
{
printf("分配失败,程序终止!");
exit(-1);
}
//创建尾结点,用来追加链表元素
PNODE pTail = pHead;//把pHead的值赋给pTail,那么pHead和pTail的值都指向头结点
//将尾结点赋空
pTail->pNext = NULL;//然后把头结点的指针域清空,这样就可以让pTail永远指向尾结点
printf("请输入您需要生成的链表结点的个数:len = ");
scanf("%d", &len);
for (int i = 0; i < len; i++)
{
printf("请输入第%d个结点的值:",i+1);
scanf("%d", &value);
//定义一个临时结点
PNODE pNew = (PNODE)malloc(sizeof(NODE));
if (NULL == pNew)
{
printf("分配失败,程序终止!
");
exit(-1);
}
/*
pNew->data = value;
pHead->pNext = pNew;
pNew->pNext = NULL;
会导致内存泄露
*/
//将值赋给临时结点的数据域
pNew->data = value;
//将带有数据的临时结点赋给尾结点
pTail->pNext = pNew;
//将临时结点指针域赋空
pNew->pNext = NULL;
//将临时结点赋给尾结点,相当于尾结点向后移动
pTail = pNew;
}
return pHead;
}
//遍历链表
void traverse_list(PNODE pHead)
{
if (is_empty(pHead))
{
printf("链表为空!
");
return;
}
//定义一个临时结点,将首结点赋给临时结点
PNODE p = pHead->pNext;
while (NULL != p)
{
//打印该节点的数据
printf("%d ", p->data);
//指针向后移动
p = p->pNext;
}
printf("
");
return;
}
//判断链表是否为空
bool is_empty(PNODE pHead)
{
//如果头结点指针域为空,则表示没有首结点
if (pHead == NULL || NULL == pHead->pNext)
return true;
return false;
}
//获取链表长度
int length_list(PNODE pHead)
{
if (is_empty(pHead))
{
printf("链表为空!
");
return -1;
}
int count = 0;//存储链表结点个数
PNODE p = pHead->pNext;//定义临时结点,存放首结点
while (p != NULL)
{
p = p->pNext;
count++;
}
return count;
}
//插入元素
//在pHead所指向链表的第pos个结点的前面插入一个新的节点
//该结点的值是value,并且pos的值从1开始
//pos的值不能超过结点个数
bool insert_list(PNODE pHead, int pos, int value)
{
if (is_empty(pHead))
{
printf("链表为空!
");
return false;
}
if (pos > length_list(pHead))
{
printf("结点不存在");
}
PNODE p = pHead;
int i = 0;
//判断链表是否为空、判断插入位置是否超出链表个数
//将p指针移动到pos插入位置
while (NULL != p && i < pos - 1)
{
p = p->pNext;
i++;
}
if (i > pos - 1 || NULL == p)
{
return false;
}
//创建新的结点
PNODE pNew = (PNODE)malloc(sizeof(NODE));
if (NULL == pNew)
{
printf("动态内存分配失败!
");
exit(-1);
}
//将值赋给即将要插入的结点
pNew->data = value;
//定义一个临时结点,用来存储pos后的结点,防止内存泄露
PNODE q = p->pNext;//此时q指向了pos结点的后一个结点
p->pNext = pNew;//将要插入的结点追加到链表的pos位置
pNew->pNext = q;//连接链表
return true;
}
//删除元素
bool delete_list(PNODE pHead, int pos, int * value)
{
PNODE p = pHead;
int i = 0;
//判断链表是否为空、判断插入位置是否超出链表个数
//将p指针移动到pos插入位置
while (NULL != p && i < pos - 1)
{
p = p->pNext;
i++;
}
if (i > pos - 1 || NULL == p)
{
return false;
}
//将要删除的结点保存,以便释放
PNODE temp = p->pNext;
//删除节点
p->pNext = temp->pNext;
*value = temp->data;
//释放节点
free(temp);
return true;
}
//链表排序
void sort_list(PNODE pHead)
{
if (is_empty(pHead))
{
printf("链表为空!
");
}
PNODE p1 = pHead->pNext;
while (p1 != NULL)
{
PNODE p2 = p1->pNext;
while (p2 != NULL)
{
if (p1->data > p2->data)
{
int temp = p1->data;
p1->data = p2->data;
p2->data = temp;
}
p2 = p2->pNext;
}
p1 = p1->pNext;
}
}
——线性结构的两种常见应用之一 — 栈【先进后出】
栈(stack) —— 由编译器自动分配施放,存放函数的参数值,局部变量的值等。
堆(heap) —— 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS(操作系统)回收
注意:它与数据结构中的堆是两回事,分配方式类似于链表。
定义
一种可以实现“先进后出”的存储结构。
分类
静态栈
动态栈
算法
压栈
出栈
应用
函数调用
中断【计算机组成原理】
表达式求值
一个栈存放运算符,一个栈存放数值,利用两个栈可以做一个计算器。
内存分配
缓冲处理
迷宫
——栈算法演示
# include <stdio.h>
# include <stdlib.h>
# include <malloc.h>
typedef struct Node
{
int data;
struct Node * pNext;
}NODE, *PNODE;
typedef struct Stack
{
PNODE pTop;
PNODE pBottom;
}STACK, *PSTACK;
void init_Stack(PSTACK pS);// 初始化栈
void push_Stack(PSTACK pS, int val);//压栈
void traverse_Stack(PSTACK pS);// 遍历栈
bool pop_Stack(PSTACK pS, int * val);//把pS所指向的栈出栈一次,并把出栈的元素存入val形参所指向的变量中,如果出栈失败,
返回false,否则返回true
bool empty_Stack(PSTACK pS);//判断栈是否为空
void clear_Stack(PSTACK pS);//清空栈
int main(void)
{
int val;
STACK S; //STACK 等价于 struct Stack
init_Stack(&S);//初始化
printf("请输入要压栈的值:
");
scanf("%d", &val);
push_Stack(&S, val);//压栈
printf("请输入要压栈的值:
");
scanf("%d", &val);
push_Stack(&S, val);
printf("遍历输出:
");//遍历输出
traverse_Stack(&S);
//清空栈
clear_Stack(&S);
printf("清空栈后输出:
");
traverse_Stack(&S);
return 0;
}
void init_Stack(PSTACK pS)
{
pS->pTop = (PNODE)malloc(sizeof(NODE));
if (pS->pTop == NULL)
{
printf("动态内存分配失败!
");
exit(-1);
}
else
{
pS->pBottom = pS->pTop;
pS->pTop->pNext = NULL;
}
}
void push_Stack(PSTACK pS, int val)
{
PNODE pNew;
pNew = (PNODE)malloc(sizeof(NODE));
pNew->data = val;
pNew->pNext = pS->pTop;
pS->pTop = pNew;
return;
}
void traverse_Stack(PSTACK pS)
{
PNODE p = pS->pTop;
while (p != pS->pBottom)
{
printf("%-5d", p->data);
p = p->pNext;
}
printf("
");
return;
}
bool pop_Stack(PSTACK pS, int * val)
{
if (empty_Stack(pS))//pS本身存放的就是S的地址
{
return false;
}
else
{
PNODE r;
r = pS->pTop;
*val = r->data;
pS->pTop = r->pNext;
free(r);
r = NULL;
return true;
}
}
bool empty_Stack(PSTACK pS)
{
if (pS->pBottom == pS->pTop)
return true;
else
return false;
}
void clear_Stack(PSTACK pS)
{
if (empty_Stack(pS))
{
return;
}
else
{
PNODE p = pS->pTop, q = NULL;
while (p != pS->pBottom)
{
q = p->pNext;
free(p);
p = q;
}
pS->pTop = pS->pBottom;
return;
}
}
——线性结构的两种常见应用之二 — 队列【先进先出】
定义:
一种可以实现“先进先出”的存储结构。
分类:
链式队列 —— 用链表实现
静态队列【数组队列】 —— 用数组实现
静态队列通常都必须是循环队列。
循环队列的讲解:
1、静态队列为什么必须是循环队列
2、循环队列需要几个参数来确定
需要两个参数:
front和rear
3、循环队列各个参数的含义
这两个参数不同场合有不同的含义:
1、队列初始化
front和rear的值都是零;
2、队列非空
front代表的是队列的第一个元素;
rear代表的是队列的最后一个有效元素的下一个元素;
3、队列为空
front和rear的值相等,但不一定是零。
4、循环队列入队伪算法讲解
两步完成:
1、将值存入rear所代表的位置
2、将rear向后移
错误的写法:rear = rear + 1;
正确的写法:rear = (rear + 1) % 数组的长度
5、循环队列出队伪算法讲解
front = (front + 1) % 数组长度
6、如何判断循环队列是否为空
front = rear,则该队列为空。
7、如何判断循环队列是否已满
1、多增加一个标识参数
2、少用一个元素【通常使用第二种方式】
如果rear和front相邻,则队列已满
用C语言为算法表示就是:
if ( (rear + 1) % 数组长度 == front)
队列已满;
else
队列不满;
队列算法:
入队
出队
队列的具体应用:
所有和时间有关的操作都与队列有关。
例如系统任务,先打开先执行。
——递归
定义:
一个函数直接或间接的调用函数本身。【用栈来实现】
函数的调用:
当在一个函数的运行期间内调用另一个函数时,在运行被调函数之前,系统需要完成三件事:
1、将所有的实际参数,返回地址等信息传递给被调函数保存。
2、为被调函数的局部变量(也包括形参)分配存储空间。
3、将控制转移到被调函数的入口。
从被调函数返回主调函数之前,系统也要完成三件事:
1、保存被调函数的返回结果。
2、释放被调函数所占的存储内存。
3、依照被调函数保存的返回地址将控制转移到被调函数。
当有多个函数相互调用时,按照”后调用先返回(栈)“的原则,上述函数之间信息传递和控制转移必须借助”栈“来实现,即
系统将整个程序运行时所需的数据空间安排在一个栈中,每当调用一个函数时,就在栈顶分配一个存储区,进行压栈操作,
当前运行的函数永远都在栈顶位置。
递归要满足的三个条件:
1、递归必须要有一个明确的终止条件。
2、该函数所处理的数据规模必须在递减。
3、这个转化必须是可解的。
循环和递归
递归:
易于理解
速度慢
存储空间大
循环:
不易理解
速度快
存储空间小
递归的应用:
树和森林就是以递归的方式来定义的
树和图的很多算法都是以递归来实现的
很多数学公式就是以递归的方式定义的
斐波那契数列
举例:
1、求阶乘
# include <stdio.h>
long f(long n)
{
if (1 == n)
return 1;
else
return f(n-1) * n;
}
int main (void)
{
int val;
scanf("%d",&val);
printf("%d的阶乘位:%d
",val,f(val));
}
2、1+2+3+……+100的和
# include <stdio.h>
int fun(int n)
{
if (1 == n)
return 1;
else
return n += fun(n-1);
}
int main(void)
{
printf("%d
",fun(100));
}
3、汉诺塔
4、走迷宫
——模块二:非线性结构
树:
树的定义:
1、有且只有一个称为根的节点。
2、有若干个互不相交的子树,这些子树本身也是一棵树。
术语:
节点
父节点
子节点
子孙
堂兄弟
深度:
树中结点的最大层次(从根节点到最底层节点的层数称之为深度)。
根节点是第一层。
叶子节点:
没有子结点的结点。
非终端节点:
实际就是非叶子结点。
度:
子节点的个数称为度。
树的分类:
一般树:
任意一个节点的子节点的个数都不受限制。
二叉树:
任意一个节点的子节点的个数最多两个,且子节点的位置不可更改。
二叉树的分类:
一般二叉树:
满二叉树:在不增加树的层数的前提下,无法再多添加一个节点的二叉树就是满二叉树。
完全二叉树:如果只是删除了满二叉树最底层最右边的连续若干个结点,这样形成的二叉树就是完全二叉树。
完全二叉树包含满二叉树。
森林:
n个互不相交的树的集合。
树的存储:
二叉树的存储:
连续存储【完全二叉树】
优点:
查找某个结点的父结点和子结点(也包括判断有没有父结点和子结点)的速度很快。
缺点:
耗用内存空间过大。
连式存储:
一般树的存储:
双亲表示法:存储父结点的下标,求父结点方便。
孩子表示法:存储子结点的指针,求子结点方便。
双亲孩子表示法:存储父结点的下标和子结点的指针,求父结点和子结点都很方便。
二叉树表示法:把一个普通树转化成二叉树来存储。
具体转换方法:
设法保证任意一个结点的左指针域指向他的第一个子结点,右指针域指向它的兄弟结点,只要能满足
此条件,就能把一个普通树转化为二叉树。一个普通树转化成的二叉树一定没有右子树。
森林的存储:
先把森林转化为二叉树,再存储为二叉树
二叉树的操作:
遍历:
先序遍历:
先访问根结点
再访问左子树
再访问右子树
中序遍历:
先遍历左子树
再访问根节点
再遍历右子树
后序遍历:
先遍历左子树
再遍历右子树
再访问根节点
示例1:
先序:ABCDEFGH
中序:BDCEAFHG
求后序:DECBHGFA
示例2:
先序:ABDGHCEFI
中序:GDHBAECIF
求后序:GHDBEIFCA
示例:
中序:BDCEAFHG
后序:DECBHGFA
求先序:ABCDEFGH
已知两种遍历序列,求原始二叉树:
通过【先序和中序】或者【中序和后序】我们可以还原出原始的二叉树,但是通过【先序和后序】是无法还原出
原始的二叉树的。
树的应用:
树是数据库中数据组织的一种重要形式
操作系统子父进程的关系本身就是一棵树
面向对象语言中类的继承关系
哈夫曼树
——二叉树的先中后序遍历
# include <stdio.h>
# include <stdlib.h>
struct BTNode
{
char data;
struct BTNode * pLchild;//p是指针 L是左 child是孩子
struct BTNode * pRchild;
};
struct BTNode * CreateBTree(void);//创建一个二叉树
void PreTraverseBTree(struct BTNode * pT);//先序遍历
void InTraverseBTree(struct BTNode * pT);//中序遍历
void PostTraverseBTree(struct BTNode * pT);//后序遍历
int main(void)
{
struct BTNode * pT = CreateBTree();//造出一个链式二叉树,并且返回二叉树的首地址
PreTraverseBTree(pT);
InTraverseBTree(pT);
PostTraverseBTree(pT);
return 0;
}
struct BTNode * CreatBTree(void)
{
//创建五个子结点
struct BTNode * pA = (struct BTNode *)malloc(sizeof(struct BTNode));
struct BTNode * pB = (struct BTNode *)malloc(sizeof(struct BTNode));
struct BTNode * pC = (struct BTNode *)malloc(sizeof(struct BTNode));
struct BTNode * pD = (struct BTNode *)malloc(sizeof(struct BTNode));
struct BTNode * pE = (struct BTNode *)malloc(sizeof(struct BTNode));
//子结点赋值
pA->data = 'A';
pB->data = 'B';
pC->data = 'C';
pD->data = 'D';
pE->data = 'E';
//子结点连接指针域
pA->pLchild = pB;
pA->pRchild = pB;
pB->pLchild = pB->pRchild = NULL;
pC->pLchild = pD;
pC->pRchild = NULL;
pD->pLchild = NULL;
pD->pRchild = pE;
pE->pLchild = pE->pRchild = NULL;
return pA;
}
void PreTraverseBTree(struct BTNode * pT)//先序遍历
{
if (NULL != pT->pLchild)
{
printf("%c
", pT->data);
if (NULL != pT->pLchild)
{
PreTraverseBTree(pT->pLchild);
}
if (NULL != pT->pRchild)
{
PreTraverseBTree(pT->pRchild);
}
}
}
void InTraverseBTree(struct BTNode * pT)//中序遍历
{
if (NULL != pT)
{
if (NULL != pT->pLchild)
{
InTraverseBTree(pT->pLchild);
}
printf("%c
", pT->data);
if (NULL != pT->pRchild)
{
InTraverseBTree(pT->pRchild);
}
}
}
void PostTraverseBTree(struct BTNode * pT)//后序遍历
{
if (NULL != pT)
{
if (NULL != pT->pLchild)
{
PostTraverseBTree(pT->pLchild);
}
if (NULL != pT->pRchild)
{
PostTraverseBTree(pT->pRchild);
}
printf("%c
", pT->data);
}
}
图:呵呵
——模块三:查找和排序
折半查找
排序:排序算法的优秀与否取决于算法的时间、空间和稳定性
冒泡排序
# include <stdio.h>
int main (void)
{
int i,j,a[5],temp;
printf("请输入五个数字:
");
for (i=0; i<5; i++)
{
scanf ("%d",&a[i]);
}
for (i=1; i<6; i++)
{
for (j=0; j<5-i; j++)
{
if (a[j] > a[j+1])
{
temp = a[j];
a[j] = a[j+1];
a[j+1] = temp;
}
}
}
printf("输出排序后的数字:
");
for (i=0; i<5; i++)
{
printf("%-4d",a[i]);
}
return 0;
}
插入排序
选择排序
# include <stdio.h>
void sort(int * a, int len)
{
int i, j, min, t;
for (i = 0; i < len - 1; i++)
{
for (min = i, j = i + 1; j<len; j++)
{
if (a[min]>a[j])
min = j;
}
if (min != i)
{
t = a[i];
a[i] = a[min];
a[min] = t;
}
}
}
int main(void)
{
int a[6] = { 5, 6, 2, 7, 9, 3 };
sort(a, 6);
for (int i = 0; i < 6; i++)
printf("%-4d", a[i]);
printf("
");
return 0;
}
快速排序
归并排序
排序和查找的关系
排序是查找的前提
排序是重点
Java中容器和数据结构的相关知识
Iterator接口
Map
哈希表
再次讨论什么是数据结构
数据结构研究的是数据的存储和数据的操作的一门学问
数据的存储分为两部分
个体的存储
个体关系的存储
从某个角度而言,数据的存储最核心的就是个体关系的存储,个体的存储可以忽略不计。
再次讨论什么是泛型
同一种逻辑结构,无论该逻辑结构是如何实现物理存储的,我们都可以对它执行相同的操作。