一、什么是数据结构?
1.1数据结构的概念
数据:能够输入到计算机中的描述客观事物的信息集合 (需要计算机处理的对象)
结构:数据之间的关系
算法:对特定问题求解的一种描述
数据结构是独立于计算机、不依赖开发语言
数据结构是研究 数据逻辑关系、存储关系、及操作的知识
1.2数据结构的三要素
1.2.1逻辑结构:
1.2.1.1概念:
数据元素之间的关系,和存储无关,独立于计算机指数据之间在逻辑上的关系,主要指邻接关系逻辑关系在考虑数据间关系的时候会涉及直接前趋和直接后继的概念.
1.2.1.2数据的逻辑结构:
线性结构:一对一的关系,除了端点之外的每一个数据都有一个前驱和一个后继
注意:线性结构要求数据是连续的
非线性结构:
树形结构:
一对多的关系,每个节点都有一个前驱和多个后继
图形结构:
多对多的关系
集合结构:
数据元素间除“同属于一个集合”外,无其它关系,一般编程时不考虑。
1.2.2存储结构:
1.2.2.1概念:
指数据在计算机内存中的分布方式.存储结构:逻辑结构在计算机中的具体实现方法,也就是说是数据在内存上实际的存储方式。存储结构是通过计算机语言所编制的程序来实现的,因而是依赖于具体的计算机语言的。
1.2.2.2 顺序存储:
将数据结构中各元素按照其逻辑顺序存放于存储器一片连续的存储空间中,如c语言的数组。
5.2.2 链式存储:
将数据结构中各元素分布到存储器的不同点,用指针方式建立它们之间的联系。
5.2.3 索引存储:
根据数据元素的特殊字段(称为关键字key),计算数据元素的存放地址,然后数据元素按地址存放
如:使用百家姓作为关键字来存储市民信息,访问时效率会比遍历所有市民高很多。
1.3 算法相关的概念
1.3.1 算法的定义
所谓的算法,就是解决问题的一种方法。在有限步骤内解决数学问题的过程
注意:计算机中处理的数据都是有限的,那些无限的概念只出现在数学模型中。
算法是不依赖于编程语言的
1.3.2 算法的特点
1.有穷性 算法是由若干指令组成的有穷序列,总是在执行若干次后结束
2.确定性 每条指令都有特定的含义 且 没有歧义,同样的输入要有同样的输出
3.可行性 算法可以在当前环境下通过有限次运行实现
4.输入输出 由 0 个输入或多个输入 有一个或多个输出
1.3.3 好的算法标准:
1.正确性 运行正常、结果正确
2.易读性 简单、易读 遵循公司命名规则、适当的注释
3.健壮性 对非法的数据和操作有良好的反应能力和处理能力
4.高效性 执行效率高、运行时间短
5.低存储 算法所占空间尽可能低
1.3.4算法的复杂度
1.3.4.1 概念
算法的复杂度分为时间复杂度和空间复杂度空间复杂度:指的就是算法在实现的过程中需要用到的额外的临时内存的大小现在技术比较成熟了,内存空间一般都是够用的,所以空间复杂度一般不考虑时间复杂度:指的是算法实现的过程中,需要耗时的多少。如果只说算法的复杂度,一般情况下,指的都是时间复杂度。程序由三种结构构成:顺序结构、分支结构和循环结构。顺序结构和分支结构中的每段代码只运行一次;循环结构中的代码的运行时间要看循环的次数。由于是估算算法的时间复杂度,相比而言,循环结构对算法的执行时间影响更大。所以,算法的时间复杂度,主要看算法中使用到的循环结构中代码循环的次数(称为“频度”)。次数越少,算法的时间复杂度越低。
1.3.4.2 时间复杂度的表示
算法的时间复杂度定义为算法中可执行语句的频度之和 记作 T(n)
语句频度是指同一代码执行的次数
T(n)是算法所需时间的一种估值,n 表示问题的规模
算法的时间复杂度的表示方式为:
O(频度); 称为 大 O 表示法
使用大O表示法的简化方式:
1.去掉运行时间中的所有常数项。
(例如 2+2n+2n^2,直接变为 2n^2+2n)
2.保留最高次幂项。
(2n^2+2n 变成 2n^2)
3.最高次幂项存在但是系数不是1,把系数改成1。
2n^2 变成 n^2
所有,上述例子中算法的时间复杂度为 O(n^2)
1.3.4.3常见的时间复杂度
时间复杂度是 O(n):
int i = 1;
int sum = 0;
for(i = 1; i <= n; i++){
sum+=i;
}
时间复杂度 O(1):
int sum = 0;
sum = (1+n)*n/2;
时间复杂度 O(n^2):
for(i = 0; i < n; i++){
for(j = 0; j < n; j++){
//时间复杂度为 O(1) 的语句
}
}
时间复杂度 O(n^3):
for(i = 0; i < n; i++){
for(j = 0; j < n; j++){
for(k = 0; k < n; k++){
//时间复杂度为 O(1) 的语句
}
}
}
时间复杂度 O(logn):
int i = 1;
while(i<n){
i *= 2;
}
时间复杂度优劣的排序:
O(1) < O(logn) < O(n) < O(nlogn) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n)
1.4顺序表
1.4.1 概念:
逻辑结构:线性结构
存储结构:顺序存储(顺序表)--基础数组实现
缺点:
空间使用不够灵活,需要提前确定数组的大小
插入和删除元素比较麻烦,因为需要批量的移动元素
优点:
按照位置查找元素比较快,因为可以通过数组下标来访问元素
1.4.2 操作:
创建线性表
插入数据元素(尾插、任意位置插入法)
删除数据元素(尾删法、任意位置删除法)
修改数据元素(根据位置、值修改)
在顺序表中查找元素(按位置查找)
顺序表的排序 --排序
清空顺序表
释放顺序表
打印顺序表中的成员----实际用不到,我们写他是用来学习阶段看现象的
1.4.3代码实现
头文件
#ifndef __SEQ_LIST_H__
#define __SEQ_LIST_H__
#include <stdio.h>
#include <stdlib.h>
#define N 5
typedef struct _Student{
int data;
}stu_t;
typedef struct _Seq_list{
stu_t s[N];
int count;
}seq_list_t;
seq_list_t *create_seq_list1();
int create_seq_list2(seq_list_t **);
int insert_seq_list_by_tail(seq_list_t *my_class, int in_data);
int print_seq_list(seq_list_t *my_class);
int insert_seq_list_by_pos(seq_list_t *my_class, int in_data, int pos);
int delete_from_deq_list_by_pos(seq_list_t *my_class, int pos);
int clean_seq_list(seq_list_t *my_class);
int destroy_seq_list(seq_list_t **my_class);
int search_data_from_seq_list(seq_list_t *my_class, int pos, int *out_data);
int update_seq_list_by_pos(seq_list_t *my_class, int pos, int new_value);
int sort_seq_list(seq_list_t *my_class, int flag);
#endif
函数
#include "seq_list.h"
//创建顺序表的方式1 使用指针函数创建
seq_list_t *create_seq_list1(){
seq_list_t *temp = (seq_list_t *)malloc(sizeof(seq_list_t));
if(NULL == temp){
printf("内存分配失败\n");
exit(-1);//结束进程
}
temp->count = 0;
return temp;
}
//创建顺序表的方式2 使用地址传参方式创建
int create_seq_list2(seq_list_t **p){
if(NULL == p){
printf("入参为 NULL, 请检查\n");
return -1;
}
*p = (seq_list_t *)malloc(sizeof(seq_list_t));
if(NULL == *p){
printf("内存分配失败\n");
return -1;
}
(*p)->count = 0;
return 0;
}
//向顺序表中插入数据的函数--尾插法
int insert_seq_list_by_tail(seq_list_t *my_class, int in_data){
//入参合理性检查
if(NULL == my_class){
printf("入参为 NULL, 请检查\n");
return -1;
}
if(my_class->count == N){
printf("表已满,插入失败\n");
return -1;
}
my_class->s[my_class->count].data = in_data;
my_class->count++;
return 0;
}
//打印顺序表中的元素
int print_seq_list(seq_list_t *my_class){
if(NULL == my_class){
printf("入参为 NULL, 请检查\n");
return -1;
}
int i = 0;
for(i = 0; i < my_class->count; i++){
printf("%d ", my_class->s[i].data);
}
printf("\n");
return 0;
}
//向顺序表中插入数据的函数--任意位置插入
int insert_seq_list_by_pos(seq_list_t *my_class, int in_data, int pos){
if(NULL == my_class){
printf("入参为 NULL, 请检查\n");
return -1;
}
if(my_class->count == N){
printf("表已满,插入失败\n");
return -1;
}
//对插入位置的合理性做检查
if(pos<0 || pos>my_class->count){
printf("插入位置不合理,请检查\n");
return -1;
}
//开始插入
//先将待插入位置后面的数据依次向后移动一步
int i = 0;
for(i = my_class->count-1; i >= pos; i--){
my_class->s[i+1] = my_class->s[i];
}
//将数据插入待插入位置
my_class->s[pos].data = in_data;
my_class->count++;
return 0;
}
int delete_from_deq_list_by_pos(seq_list_t *my_class, int pos){
if(NULL == my_class){
printf("入参为 NULL, 请检查\n");
return -1;
}
if(my_class->count == 0){
printf("表已空,删除失败\n");
return -1;
}
if(pos<0 || pos>=my_class->count){
printf("删除位置不合理,请检查\n");
return -1;
}
//执行删除操作
//从待删除位置开始,后面的元素依次向前移动一步
int i = 0;
for(i = pos; i < my_class->count-1; i++){
my_class->s[i] = my_class->s[i+1];
}
my_class->count--;
return 0;
}
//清空顺序表
int clean_seq_list(seq_list_t *my_class){
if(NULL == my_class){
printf("入参为 NULL, 请检查\n");
return -1;
}
my_class->count = 0;
return 0;
}
//释放顺序表
int destroy_seq_list(seq_list_t **my_class){
if(NULL == my_class || NULL == *my_class){
printf("入参为 NULL, 请检查\n");
return -1;
}
free(*my_class);
*my_class = NULL;//之所以传二级指针 是为了这一步操作
return 0;
}
//查找顺序表中指定位置的数据
int search_data_from_seq_list(seq_list_t *my_class, int pos, int *out_data){
if(NULL == my_class || NULL == out_data){
printf("入参为 NULL, 请检查\n");
return -1;
}
if(pos<0 || pos>=my_class->count){
printf("查找位置不合理,请检查\n");
return -1;
}
*out_data = my_class->s[pos].data;
return 0;
}
//按照位置修改元素
int update_seq_list_by_pos(seq_list_t *my_class, int pos, int new_value){
if(NULL == my_class){
printf("入参为 NULL, 请检查\n");
return -1;
}
if(pos<0 || pos>=my_class->count){
printf("修改位置不合理,请检查\n");
return -1;
}
my_class->s[pos].data = new_value;
return 0;
}
//顺序表排序
// flag 0 升序 1 降序
int sort_seq_list(seq_list_t *my_class, int flag){
if(NULL == my_class){
printf("入参为 NULL, 请检查\n");
return -1;
}
int i = 0;
int j = 0;
stu_t temp;
if(0 == flag){//升序
for(i = 0; i < my_class->count-1; i++){
for(j = 0; j < my_class->count-1-i; j++){
if(my_class->s[j].data > my_class->s[j+1].data){
temp = my_class->s[j];
my_class->s[j] = my_class->s[j+1];
my_class->s[j+1] = temp;
}
}
}
}else if(1 == flag){//降序
for(i = 0; i < my_class->count-1; i++){
for(j = 0; j < my_class->count-1-i; j++){
if(my_class->s[j].data < my_class->s[j+1].data){
temp = my_class->s[j];
my_class->s[j] = my_class->s[j+1];
my_class->s[j+1] = temp;
}
}
}
}
return 0;
}
main.c
#include "seq_list.h"
int main(){
seq_list_t *my_class = NULL;
//my_class = create_seq_list1();
//创建顺序表
create_seq_list2(&my_class);
//向顺序表中插入数据
insert_seq_list_by_tail(my_class, 10);
insert_seq_list_by_tail(my_class, 20);
insert_seq_list_by_tail(my_class, 30);
//打印顺序表中的元素
print_seq_list(my_class);
int num = 0;
search_data_from_seq_list(my_class, 1, &num);
printf("num = %d\n", num);
//将下标为1的元素的值修改成 520
update_seq_list_by_pos(my_class, 1, 520);
//打印顺序表中的元素
print_seq_list(my_class);
//升序排序
sort_seq_list(my_class, 0);
//打印顺序表中的元素
print_seq_list(my_class);
//升序排序
sort_seq_list(my_class, 1);
//打印顺序表中的元素
print_seq_list(my_class);
delete_from_deq_list_by_pos(my_class, 1);
//打印顺序表中的元素
print_seq_list(my_class);
//清空顺序表
clean_seq_list(my_class);
//打印顺序表中的元素
print_seq_list(my_class);
//释放顺序表
destroy_seq_list(&my_class);
printf("%p\n", my_class);
return 0;
}
1.5链表
1.5.1图示:
1.5.2链表相关的操作:
1.创建链表
2.清空链表
3.销毁链表
4.头插法
5.尾插法
6.任意位置插入法
7.头删法
8.尾删法
9.任意位置删除法
10.查询链表中是否有想要的数据(在链表中获取指定位置的数据)
11.按照位置修改链表中的数据
12.按照值修改链表中的数据
13.两个链表合并
14.链表的排序
15.链表的翻转
16.遍历链表的节点----学习阶段看现象用的
1.5.3操作示意图:
1.5.3.1 链表插入数据示意图
1.5.3.2链表删除数据示意图
1.5.3.3链表翻转示意图
1.5.3.4链表的排序示意图
1.5.3.5 代码实现
头文件
#ifndef __LINK_LIST_H__
#define __LINK_LIST_H__
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct __Node{
int data;//数据域 课上以int为例 实际开发过程中可以自己封装结构体
struct __Node *next;//指针域 注意此时还不认是 node_t 不能使用
}node_t;
int create_node(node_t **p, int in_data);
int insert_data_by_head(node_t *phead, int in_data);
int print_link_list(node_t *phead);
int insert_data_by_tail(node_t *phead, int in_data);
int insert_data_by_pos(node_t *phead, int in_data, int pos);
int del_from_list_by_head(node_t *phead);
int del_from_list_by_tail(node_t *phead);
int del_from_list_by_pos(node_t *phead, int pos);
int get_data_from_list(node_t *phead, int pos, int *num);
int update_list_by_pos(node_t *phead, int pos, int new_data);
int update_list_by_value(node_t *phead, int old_value, int new_value);
int clean_list(node_t *phead);
int destroy_list(node_t **p);
int merge_two_list(node_t *phead1, node_t **phead2);
int overturn_list(node_t *phead);
int sort_list(node_t *phead);
#endif
函数:
#include "link_list.h"
//创建节点的函数
int create_node(node_t **p, int in_data){
if(NULL == p){
printf("入参为NULL, 请检查\n");
return -1;
}
*p = (node_t *)malloc(sizeof(node_t));
if(NULL == *p){
printf("内存分配失败\n");
exit(-1);
}
(*p)->data = in_data;
(*p)->next = NULL;
return 0;
}
//头插法
int insert_data_by_head(node_t *phead, int in_data){
if(NULL == phead){
printf("入参为NULL, 请检查\n");
return -1;
}
//创建新节点
node_t *pnew = NULL;
create_node(&pnew, in_data);
//执行头插操作
pnew->next = phead->next;
phead->next = pnew;
return 0;
}
//遍历链表
int print_link_list(node_t *phead){
if(NULL == phead){
printf("入参为NULL, 请检查\n");
return -1;
}
node_t *ptemp = phead;
while(NULL != ptemp->next){
ptemp = ptemp->next;
printf("%d ", ptemp->data);
}
printf("\n");
return 0;
}
//尾插法插入数据
int insert_data_by_tail(node_t *phead, int in_data){
if(NULL == phead){
printf("入参为NULL, 请检查\n");
return -1;
}
//创建新节点
node_t *pnew = NULL;
create_node(&pnew, in_data);
//执行尾插操作
node_t *ptemp = phead;
//遍历链表找到链表的尾节点
while(ptemp->next != NULL){
ptemp = ptemp->next;
}
//让尾结点的指针域指向新节点
ptemp->next = pnew;
return 0;
}
//任意位置插入法
int insert_data_by_pos(node_t *phead, int in_data, int pos){
if(NULL == phead){
printf("入参为NULL, 请检查\n");
return -1;
}
if(pos<=0){
printf("插入位置不合理,请检查\n");
return -1;
}
//找到待插入位置的前一节点
node_t *ptemp = phead;
int i = 0;
for(i = 0; i < pos-1; i++){
ptemp = ptemp->next;
if(NULL == ptemp){
break;
}
}
//如果是由于break导致的循环结束 说明位置不合理
if(i < pos-1){
printf("插入位置不合理,请检查\n");
return -1;
}
//位置合理 创建新节点
node_t *pnew = NULL;
create_node(&pnew, in_data);
//将新节点插入
pnew->next = ptemp->next;
ptemp->next = pnew;
return 0;
}
//头删法
int del_from_list_by_head(node_t *phead){
if(NULL == phead){
printf("入参为NULL, 请检查\n");
return -1;
}
//判断是否有数据用来删除
if(NULL == phead->next){
printf("链表中没有数据 头删失败\n");
return -1;
}
//定义一个指针 pdel 保存 第一个数据节点的地址
node_t *pdel = phead->next;
phead->next = phead->next->next;
free(pdel);
pdel = NULL;
return 0;
}
//尾删法
int del_from_list_by_tail(node_t *phead){
if(NULL == phead){
printf("入参为NULL, 请检查\n");
return -1;
}
//判断是否有数据用来删除
if(NULL == phead->next){
printf("链表中没有数据 尾删失败\n");
return -1;
}
//找到最后一个节点的前一节点
node_t *ptemp = phead;
while(NULL != ptemp->next->next){
ptemp = ptemp->next;
}
free(ptemp->next);
ptemp->next = NULL;
return 0;
}
//任意位置删除法
int del_from_list_by_pos(node_t *phead, int pos){
if(NULL == phead){
printf("入参为NULL, 请检查\n");
return -1;
}
if(pos <= 0 ){
printf("删除位置不合理 请检查\n");
return -1;
}
node_t *ptemp = phead;
int i = 0;
//找到待删除位置的前一节点
for(i = 0; i < pos-1; i++){
ptemp = ptemp->next;
if(ptemp->next == NULL){
break;
}
}
if(i < pos-1){
printf("删除位置不合理 请检查\n");
return -1;
}
//执行删除操作
node_t *pdel = ptemp->next;
ptemp->next = pdel->next;
free(pdel);
pdel = NULL;
return 0;
}
//获取指定位置的数据
int get_data_from_list(node_t *phead, int pos, int *num){
if(NULL == phead){
printf("入参为NULL, 请检查\n");
return -1;
}
if(pos <= 0 ){
printf("获取数据的位置不合理 请检查\n");
return -1;
}
node_t *ptemp = phead;
int i = 0;
//找到要获取数据的节点 并做位置合理性的检查
for(i = 0; i < pos; i++){
ptemp = ptemp->next;
if(NULL == ptemp){
printf("获取数据的位置不合理,请检查\n");
return -1;
}
}
//如果运行到这儿了 说明位置合理
*num = ptemp->data;
return 0;
}
//按照位置修改链表中的数据
int update_list_by_pos(node_t *phead, int pos, int new_data){
if(NULL == phead){
printf("入参为NULL, 请检查\n");
return -1;
}
if(pos <= 0 ){
printf("修改数据的位置不合理 请检查\n");
return -1;
}
node_t *ptemp = phead;
int i = 0;
//找到要修改数据的节点 并做位置合理性的检查
for(i = 0; i < pos; i++){
ptemp = ptemp->next;
if(NULL == ptemp){
printf("修改数据的位置不合理,请检查\n");
return -1;
}
}
//如果运行到这儿了 说明位置合理
ptemp->data = new_data;
return 0;
}
//根据值修改链表中的数据
int update_list_by_value(node_t *phead, int old_value, int new_value){
if(NULL == phead){
printf("入参为NULL, 请检查\n");
return -1;
}
//遍历链表
node_t *ptemp = phead;
while(ptemp->next != NULL){
ptemp = ptemp->next;
if(ptemp->data == old_value){
ptemp->data = new_value;
}
}
return 0;
}
//清空链表 --释放所有数据节点 保留头结点
int clean_list(node_t *phead){
if(NULL == phead){
printf("入参为NULL, 请检查\n");
return -1;
}
//循环头删所有数据节点
node_t *pdel = NULL;
while(phead->next != NULL){
pdel = phead->next;
phead->next = pdel->next;
free(pdel);
}
pdel = NULL;
return 0;
}
//销毁链表
int destroy_list(node_t **p){
if(NULL == p || NULL == *p){
printf("入参为NULL, 请检查\n");
return -1;
}
//防止先调用销毁没调用清空导致的内存泄漏
//可以在释放头结点之前 先调用一下清空的函数
clean_list(*p);
free(*p);
*p = NULL;
return 0;
}
//两个链表合并
int merge_two_list(node_t *phead1, node_t **phead2){
if(NULL == phead1 || NULL == phead2 || NULL == *phead2){
printf("入参为NULL, 请检查\n");
return -1;
}
//先找到第一个链表的最后一个节点
node_t *ptemp = phead1;
while(ptemp->next != NULL){
ptemp = ptemp->next;
}
//让第一个链表的最后一个节点的指针域指向第二个链表的第一个数据节点
ptemp->next = (*phead2)->next;
//释放第二个链表的 头结点
free(*phead2);
*phead2 = NULL;
return 0;
}
//链表的翻转
int overturn_list(node_t *phead){
if(NULL == phead){
printf("入参为NULL, 请检查\n");
return -1;
}
if(phead->next == NULL){
printf("链表为空 无需翻转\n");
return -1;
}
if(phead->next->next == NULL){
printf("链表只有一个数据节点 无需翻转\n");
return -1;
}
node_t *p = phead->next;
node_t *q = p->next;
p->next = NULL;
while(NULL != q){
p = q->next;
q->next = phead->next;
phead->next = q;
q = p;
}
printf("翻转完成\n");
return 0;
}
//链表的排序 --以升序为例
int sort_list(node_t *phead){
if(NULL == phead){
printf("入参为NULL, 请检查\n");
return -1;
}
if(phead->next == NULL){
printf("链表为空 无需排序\n");
return -1;
}
if(phead->next->next == NULL){
printf("链表只有一个数据节点 无需排序\n");
return -1;
}
int temp = 0;
node_t *p = phead->next;
node_t *q = p->next;
while(NULL != p->next){
while(NULL != q){
if(q->data < p->data){
//交换数据域
temp = p->data;
p->data = q->data;
q->data = temp;
}
q = q->next;
}
p = p->next;
q = p->next;
}
printf("升序排序完成\n");
return 0;
}
main.c
#include "link_list.h"
int main(){
node_t *phead = NULL;
//创建头结点
create_node(&phead, -1);
//头插法插入数据
insert_data_by_head(phead, 10);
insert_data_by_head(phead, 20);
insert_data_by_head(phead, 30);
insert_data_by_head(phead, 40);
insert_data_by_head(phead, 50);
//尾插法插入数据
insert_data_by_tail(phead, 520);
insert_data_by_tail(phead, 1314);
//任意位置插入
insert_data_by_pos(phead, 111, 8);
//遍历链表
print_link_list(phead);
//头删法删除数据
del_from_list_by_head(phead);
//遍历链表
print_link_list(phead);
//尾删法删除数据
del_from_list_by_tail(phead);
//遍历链表
print_link_list(phead);
//任意位置删除数据
del_from_list_by_pos(phead,3);
//遍历链表
print_link_list(phead);
int num = 0;
get_data_from_list(phead, 6, &num);
printf("num = %d\n", num);
//根据位置修改数据
update_list_by_pos(phead, 4, 40);
//遍历链表
print_link_list(phead);
//根据值修改数据
update_list_by_value(phead, 40, 520);
//遍历链表
print_link_list(phead);
printf("-------------------------\n");
node_t *phead2 = NULL;
create_node(&phead2, -1);
insert_data_by_head(phead2, 111);
insert_data_by_head(phead2, 222);
insert_data_by_head(phead2, 333);
merge_two_list(phead, &phead2);
//遍历链表
print_link_list(phead);
printf("phead2 = %p\n", phead2);
printf("-------------------------\n");
//链表翻转
overturn_list(phead);
//遍历链表
print_link_list(phead);
//链表升序排序
sort_list(phead);
//遍历链表
print_link_list(phead);
//清空链表
clean_list(phead);
//遍历链表
print_link_list(phead);
//销毁链表
destroy_list(&phead);
printf("phead = %p\n", phead);
return 0;
}
1.6循环链表(基本上和单链表一样就不做各种操作,用一个练习代替了)
1.6.1图解
1.6.2练习
循环链表练习:
一位叫约瑟夫的将军,战斗中,连同手下士兵一起被敌人俘虏了,
手下的士兵宁死不投降,所以,约瑟夫将军想出了一个办法,
让大家站成一个圈,从第一个人开始数数,谁数到7了,谁就自杀,
自杀之后,他的下一个人重新从1开始数,遇到7,再自杀,直到最后只能剩下一个人。
最后活下来的是约瑟夫,然后他不想死,他投降了。
这种圈,在现今社会中被叫做,"约瑟夫环"。
要求,编写程序,命令行 输入 总人数(>=2) 及 数到几(>=2)自杀,
程序自动计算,将第 n 次 杀掉的 第m个人输出
最后再输出剩下的是几号
例如:
输入 5人 数到3 自杀,
程序输出:
第 1 次杀的人是 3 号;
第 2 次杀的人是 1 号;
第 3 次杀的人是 5 号;
第 4 次杀的人是 2 号;
最后剩下的是 4 号
1.6.2.1代码实现:
头文件:
#ifndef _MYHEAD_H__
#define __MYHEAD_H__
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct _XUN_HU{
int data;
struct _XUN_HU *next;
}xun_hun_t;
int create_xun_hun(xun_hun_t **phead);
int insert_xun_hun(xun_hun_t *phead,int in_data);
int printf_xun_hun(xun_hun_t *phead);
int yue_se_fu_xun_hun(xun_hun_t *phead,int n,int m);
#endif
函数:
#include "myhead.h"
int create_xun_hun(xun_hun_t **phead)
{
if(phead==NULL){
printf("1地址传参错误,请检查\n");
return -1;
}
*phead=(xun_hun_t *)malloc(sizeof(xun_hun_t));
(*phead)->next=*phead;
(*phead)->data=1;
return 0;
}
int insert_xun_hun(xun_hun_t *phead,int in_data)
{
if(phead==NULL){
printf("2地址传参错误,请检查\n");
return -1;
}
xun_hun_t *temp=(xun_hun_t *)malloc(sizeof(xun_hun_t));
temp->next=NULL;
temp->next=phead->next;
phead->next=temp;
temp->data=in_data;
return 0;
}
int printf_xun_hun(xun_hun_t *phead)
{
if(phead==NULL){
printf("3地址传参错误,请检查\n");
return -1;
}
xun_hun_t *flags=phead;
while(phead->next!=flags){
printf("%d ",phead->data);
phead=phead->next;
}
printf("%d\n",phead->data);
return 0;
}
//n表示从几开始数,m表示数多少个数
int yue_se_fu_xun_hun(xun_hun_t *phead,int n,int m)
{
if(phead==NULL){
printf("3地址传参错误,请检查\n");
return -1;
}
xun_hun_t *flags=phead;
int i=0;
while(phead->next!=phead&&i<n-1){
phead=phead->next;
i++;
}
//printf("%d\n",phead->data);
i=0;
while(phead!=phead->next){
phead=phead->next;
i++;
if(i==m-2){
xun_hun_t *temp=phead->next;
phead->next=phead->next->next;
printf("%d ",temp->data);
free(temp);
temp=NULL;
i=0;
phead=phead->next;
}
}
printf("%d\n",phead->data);
return 0;
}
main.c:
#include "myhead.h"
int main(int argc, char const *argv[])
{
xun_hun_t *phead=NULL;
create_xun_hun(&phead);
insert_xun_hun(phead,8);
insert_xun_hun(phead,7);
insert_xun_hun(phead,6);
insert_xun_hun(phead,5);
insert_xun_hun(phead,4);
insert_xun_hun(phead,3);
insert_xun_hun(phead,2);
printf_xun_hun(phead);
yue_se_fu_xun_hun(phead,3,3);
printf_xun_hun(phead);
return 0;
}
1.7双向循环链表(由于步骤和前面差不多,就只讲解一下思路)
1.7.1 双向链表示意图
1.7.2双向链表插入节点流程图
1.7.3双向链表删除节点流程图
1.8栈
1.8.1 概念
栈是一种 先进后出 的数据结构 FILO(first in last out)
1.8.2栈的存储结构
顺序存储的栈,我们称之为顺序栈,
是基于数组实现的,一般会配合一个 "栈顶指针" top 来使用
顺序栈本身相当于对顺序表操作的一个约束:只允许在同一端插入和删除元素。
顺序栈的操作:
创建栈
清空栈
销毁栈
向栈中插入元素:入栈/压栈(push)
判断栈是否为满
获取栈中的元素:出栈/弹栈(pop)
判断栈是否为空
打印栈中所有元素--学习阶段看现象用的
1.8.3代码实现
头文件
#ifndef __SEQ_STACK_H__
#define __SEQ_STACK_H__
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define N 3
typedef struct _STACK{
int s[N];//栈的数组 课上以int为例
int top;//栈顶指针 作为数组下标使用
}stack_t;
int create_stack(stack_t **p);
int print_stack(stack_t *my_stack);
int is_full(stack_t *my_stack);
int push_stack(stack_t *my_stack, int value);
int pop_stack(stack_t *my_stack, int *num);
int is_empty(stack_t *my_stack);
int clean_stack(stack_t *my_stack);
int destroy_stack(stack_t **my_stack);
#endif
函数:
#include "seq_stack.h"
//创建栈
int create_stack(stack_t **p){
if(NULL == p){
printf("入参为NULL 请检查\n");
return -1;
}
*p = (stack_t *)malloc(sizeof(stack_t));
if(NULL == *p){
printf("内存分配失败\n");
exit(-1);
}
(*p)->top = 0;
return 0;
}
//入栈
int push_stack(stack_t *my_stack, int value){
if(NULL == my_stack){
printf("入参为NULL 请检查\n");
return -1;
}
if(is_full(my_stack)){
printf("栈已满 入栈失败\n");
return -1;
}
my_stack->s[my_stack->top] = value;
my_stack->top++;
return 0;
}
//判断栈是否为满 返回 1 满 返回 0 未满
int is_full(stack_t *my_stack){
if(NULL == my_stack){
printf("入参为NULL 请检查\n");
return -1;
}
return my_stack->top==N?1:0;
}
//出栈
int pop_stack(stack_t *my_stack, int *num){
if(NULL == my_stack){
printf("入参为NULL 请检查\n");
return -1;
}
if(is_empty(my_stack)){
printf("栈已空 出栈失败\n");
return -1;
}
my_stack->top--;
*num = my_stack->s[my_stack->top];
return 0;
}
//判断栈是否为空 返回 1 空 返回 0 非空
int is_empty(stack_t *my_stack){
if(NULL == my_stack){
printf("入参为NULL 请检查\n");
return -1;
}
return my_stack->top==0?1:0;
}
//打印栈中所有元素
int print_stack(stack_t *my_stack){
if(NULL == my_stack){
printf("入参为NULL 请检查\n");
return -1;
}
int i = 0;
for(i = 0; i < my_stack->top; i++){
printf("%d ", my_stack->s[i]);
}
printf("\n");
return 0;
}
//清空栈 ---top置0 即可
int clean_stack(stack_t *my_stack){
if(NULL == my_stack){
printf("入参为NULL 请检查\n");
return -1;
}
my_stack->top = 0;
return 0;
}
//销毁栈
int destroy_stack(stack_t **my_stack){
if(NULL == my_stack || NULL == *my_stack){
printf("入参为NULL 请检查\n");
return -1;
}
free(*my_stack);
*my_stack = NULL;
return 0;
}
main.c:
#include "seq_stack.h"
int main(){
stack_t *my_stack = NULL;
//创建栈
create_stack(&my_stack);
//元素入栈
push_stack(my_stack, 10);
push_stack(my_stack, 20);
push_stack(my_stack, 30);
push_stack(my_stack, 40);//这次会失败 因为栈已经满了
//遍历栈
print_stack(my_stack);
int num = 0;
pop_stack(my_stack, &num);
printf("num = %d\n", num);
pop_stack(my_stack, &num);
printf("num = %d\n", num);
pop_stack(my_stack, &num);
printf("num = %d\n", num);
pop_stack(my_stack, &num);//会失败 因为栈已经空了
printf("num = %d\n", num);
//在入栈两个元素
push_stack(my_stack, 10);
push_stack(my_stack, 20);
//遍历栈
print_stack(my_stack);
//清空栈
clean_stack(my_stack);
//遍历栈
print_stack(my_stack);
//销毁栈
destroy_stack(&my_stack);
printf("my_stack = %p\n", my_stack);
return 0;
}
1.8.3链式栈
1.8.4.1概念
链式存储的栈,我们称之为链式栈,
是基于链表实现的
链式栈本身相当于对链表表操作的一个约束:只允许在同一端插入和删除元素。
如果采用链表尾作为入栈和出栈的端点,那么入栈和出栈都需要遍历,时间复杂度都是O(n)
所以一般链式栈都是 使用链表头部进行 入栈和出栈 时间复杂度是 O(1)
1.8.4.2顺序栈的操作:
创建栈
清空栈
销毁栈
向栈中插入元素:入栈/压栈(push)
获取栈中的元素:出栈/弹栈(pop)
判断栈是否为空
打印栈中所有元素--学习阶段看现象用的
1.8.4.3顺序栈代码实现:
头文件:
#ifndef __LINK_STACK_H__
#define __LINK_STACK_H__
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct _Node{
int data;
struct _Node *next;
}node_t;
typedef struct _Stack{
unsigned int count;
node_t *top;
}stack_t;
int create_stack(stack_t **my_stack);
int push_stack(stack_t *my_stack, int value);
int pop_stack(stack_t *my_stack, int *num);
int is_empty(stack_t *my_stack);
int clean_stack(stack_t *my_stack);
int destroy_stack(stack_t **my_stack);
int print_stack(stack_t *my_stack);
#endif
函数:
#include "link_stack.h"
//创建栈
int create_stack(stack_t **my_stack){
if(NULL == my_stack){
printf("入参为NULL 请检查\n");
return -1;
}
*my_stack = (stack_t *)malloc(sizeof(stack_t));
if(NULL == *my_stack){
printf("内存分配失败\n");
exit(-1);
}
(*my_stack)->count = 0;
(*my_stack)->top = NULL;
return 0;
}
//入栈
int push_stack(stack_t *my_stack, int value){
if(NULL == my_stack){
printf("入参为NULL 请检查\n");
return -1;
}
//先创建新的 数据节点
node_t *pnew = (node_t *)malloc(sizeof(node_t));
if(NULL == pnew){
printf("内存分配失败\n");
exit(-1);
}
pnew->data = value;
pnew->next = NULL;
//将新节点头插到链表中
pnew->next = my_stack->top;
my_stack->top = pnew;
my_stack->count++;
return 0;
}
//出栈
int pop_stack(stack_t *my_stack, int *num){
if(NULL == my_stack){
printf("入参为NULL 请检查\n");
return -1;
}
if(is_empty(my_stack)){
printf("栈为空 出栈失败\n");
return -1;
}
//数据出栈
*num = my_stack->top->data;
//删除已经出栈数据的节点--头删
node_t *pdel = my_stack->top;
my_stack->top = pdel->next;
free(pdel);
pdel = NULL;
my_stack->count--;
return 0;
}
//判断栈是否为空 返回 1 空 返回 0 非空
int is_empty(stack_t *my_stack){
if(NULL == my_stack){
printf("入参为NULL 请检查\n");
return -1;
}
return my_stack->top == NULL ? 1 : 0;
}
//清空栈
int clean_stack(stack_t *my_stack){
if(NULL == my_stack){
printf("入参为NULL 请检查\n");
return -1;
}
node_t *pdel = NULL;
//循环头删 删除所有的 数据节点
while(NULL != my_stack->top){
pdel = my_stack->top;
my_stack->top = pdel->next;
free(pdel);
}
pdel = NULL;
//个数清零
my_stack->count = 0;
return 0;
}
//销毁栈
int destroy_stack(stack_t **my_stack){
if(NULL == *my_stack || NULL == my_stack){
printf("入参为NULL 请检查\n");
return -1;
}
//先调用清空
clean_stack(*my_stack);
free(*my_stack);
*my_stack = NULL;
return 0;
}
//遍历栈
int print_stack(stack_t *my_stack){
if(NULL == my_stack){
printf("入参为NULL 请检查\n");
return -1;
}
node_t *ptemp = my_stack->top;
while(NULL != ptemp){
printf("%d ", ptemp->data);
ptemp = ptemp->next;
}
printf("\n");
return 0;
}
main.c:
#include "link_stack.h"
int main(){
stack_t *my_stack = NULL;
//创建栈
create_stack(&my_stack);
//数据入栈
push_stack(my_stack, 10);
push_stack(my_stack, 20);
push_stack(my_stack, 30);
//遍历栈
print_stack(my_stack);
//数据出栈
int num = 0;
pop_stack(my_stack, &num);
printf("num = %d\n", num);
pop_stack(my_stack, &num);
printf("num = %d\n", num);
pop_stack(my_stack, &num);
printf("num = %d\n", num);
pop_stack(my_stack, &num);
printf("num = %d\n", num);
//再入栈两个元素
push_stack(my_stack, 520);
push_stack(my_stack, 1314);
//遍历栈
print_stack(my_stack);
//清空栈
clean_stack(my_stack);
//遍历栈
print_stack(my_stack);
//销毁栈
destroy_stack(&my_stack);
printf("my_stack = %p\n", my_stack);
return 0;
1.9队列
1.9.1 概念
队列是一种先进先出的结构。 FIFO(first in first out)
1.9.1.1队列的逻辑结构
线性结构
1.9.1.2队列的存储结构
顺序队列是基于顺序表配个两个下标,一个是队列头 front,一个是队列尾 rear。
顺序队列相当于对顺序表操作的一种约束:一端插入,另一端删除。
一般这种顺序队列我们不使用,因为 入队列时 rear++ 出队列时 front++
相当于每块空间指使用了一次,即使出队列了,空间也不会复用了,相当于一次性的
1.9.1.3 循环队列
这种结构的队列可以让空间复用起来。
每次插入完数据 不要执行 rear++ 而改成 rear = (reat+1)%N
每次删除完数据 不要执行 front++ 而改成 front = (front+1)%N
判断队列空 rear == front
判断队列满 (rear+1)%N == front (浪费了一个存储空间用来和判断空作区分,方便写代
1.9.1.4循序队列的操作:
创建队列
清空队列
销毁队列
入队列
判断队列是否为满
出队列
判断队列是否为空
1.9.1.5顺序队列的代码实现
头文件
#ifndef __SEQ_QUEUE_H__
#define __SEQ_QUEUE_H__
#include <stdio.h>
#include <stdlib.h>
#define N 5
typedef struct _Queue{
int s[N];
int front; //头
int rear; //尾
}queue_t;
int create_queue(queue_t **p);
int push_queue(queue_t *my_queue, int in_data);
int is_full(queue_t *my_queue);
int pop_queue(queue_t *my_queue, int *out_data);
int is_empty(queue_t *my_queue);
int clean_queue(queue_t *my_queue);
int destroy_queue(queue_t **my_queue);
int print_queue(queue_t *my_queue);
#endif
函数
#include "seq_queue.h"
//创建队列
int create_queue(queue_t **p){
if(NULL == p){
printf("入参为 NULL,请检查\n");
return -1;
}
*p = (queue_t *)malloc(sizeof(queue_t));
if(NULL == *p){
printf("内存分配失败\n");
exit(-1);
}
(*p)->front = 0;
(*p)->rear = 0;
return 0;
}
//入队列
int push_queue(queue_t *my_queue, int in_data){
if(NULL == my_queue){
printf("入参为 NULL,请检查\n");
return -1;
}
if(is_full(my_queue)){
printf("队列满 入队列失败\n");
return -1;
}
//执行入队列操作
printf("入之前:front = %d rear = %d\n", my_queue->front, my_queue->rear);
my_queue->s[my_queue->rear] = in_data;
my_queue->rear = (my_queue->rear+1)%N;
printf("入之后:front = %d rear = %d\n", my_queue->front, my_queue->rear);
return 0;
}
//判断队列满 返回1满 0 未满
int is_full(queue_t *my_queue){
if(NULL == my_queue){
printf("入参为 NULL,请检查\n");
return -1;
}
return (my_queue->rear+1)%N == my_queue->front ? 1 : 0;
}
//出队列
int pop_queue(queue_t *my_queue, int *out_data){
if(NULL == my_queue || NULL == out_data){
printf("入参为 NULL,请检查\n");
return -1;
}
if(is_empty(my_queue)){
printf("队列空 出队列失败\n");
return -1;
}
//执行出队列操作
*out_data = my_queue->s[my_queue->front];
my_queue->front = (my_queue->front+1)%N;
return 0;
}
//判断队列空 返回1空 0 非空
int is_empty(queue_t *my_queue){
if(NULL == my_queue){
printf("入参为 NULL,请检查\n");
return -1;
}
return my_queue->front == my_queue->rear ? 1 : 0;
}
//清空队列
int clean_queue(queue_t *my_queue){
if(NULL == my_queue){
printf("入参为 NULL,请检查\n");
return -1;
}
my_queue->front = 0;
my_queue->rear = 0;
return 0;
}
//销毁队列
int destroy_queue(queue_t **my_queue){
if(NULL == my_queue || NULL == *my_queue){
printf("入参为 NULL,请检查\n");
return -1;
}
free(*my_queue);
*my_queue = NULL;
return 0;
}
//遍历队列
int print_queue(queue_t *my_queue){
if(NULL == my_queue){
printf("入参为 NULL,请检查\n");
return -1;
}
int i = 0;
for(i = my_queue->front; (i%N)!=my_queue->rear ;i++){
printf("%d ", my_queue->s[i%N]);
}
printf("\n");
return 0;
}
main.c
#include "seq_queue.h"
int main(){
queue_t *my_queue = NULL;
//创建队列
create_queue(&my_queue);
//数据入队列
push_queue(my_queue, 10);
push_queue(my_queue, 20);
push_queue(my_queue, 30);
push_queue(my_queue, 40);
//push_queue(my_queue, 50);//队列满
//遍历队列
print_queue(my_queue);
//数据出队列
int num = 0;
pop_queue(my_queue, &num);
printf("num = %d\n", num);
pop_queue(my_queue, &num);
printf("num = %d\n", num);
pop_queue(my_queue, &num);
printf("num = %d\n", num);
pop_queue(my_queue, &num);
printf("num = %d\n", num);
//pop_queue(my_queue, &num);//失败 队列空
//printf("num = %d\n", num);
//遍历队列
print_queue(my_queue);
//再入队列几个数据
push_queue(my_queue, 50);
push_queue(my_queue, 60);
push_queue(my_queue, 70);
push_queue(my_queue, 80);
//遍历队列
print_queue(my_queue);
//清空队列
clean_queue(my_queue);
//遍历队列
print_queue(my_queue);
//销毁队列
destroy_queue(&my_queue);
printf("my_queue = %p\n", my_queue);
return 0;
}
1.10链式队列
1.10.1概念:
链式队列是基于链表实现的。
链式队列相当于对链表操作的一种约束:一端插入,另一端删除。
一般使用 尾插头删法
1.10.2链式队列的操作:
创建队列
清空队列
销毁队列
入队列
出队列
判断队列是否为空
遍历队列--学习阶段看现象用的
1.10.3代码实现
头文件
#ifndef __LINK_QUEUE_H__
#define __LINK_QUEUE_H__
#include <stdio.h>
#include <stdlib.h>
typedef struct _Node{
int data;
struct _Node *next;
}node_t;
typedef struct _Queue{
node_t *front;
node_t *rear;
}queue_t;
int create_queue(queue_t **p);
int push_queue(queue_t *my_queue, int in_data);
int print_queue(queue_t *my_queue);
int pop_queue(queue_t *my_queue, int *out_data);
int is_empty(queue_t *my_queue);
int clean_queue(queue_t *my_queue);
int destroy_queue(queue_t **my_queue);
#endif
函数
#include "link_queue.h"
//创建队列
int create_queue(queue_t **p){
if(NULL == p){
printf("入参为NULL, 请检查\n");
return -1;
}
*p = (queue_t *)malloc(sizeof(queue_t));
if(NULL == *p){
printf("内存分配失败\n");
exit(-1);
}
(*p)->front = NULL;
(*p)->rear = NULL;
return 0;
}
//数据入队列
int push_queue(queue_t *my_queue, int in_data){
if(NULL == my_queue){
printf("入参为NULL, 请检查\n");
return -1;
}
//先创建数据节点
node_t *pnew = (node_t *)malloc(sizeof(node_t));
if(NULL == pnew){
printf("内存分配失败\n");
exit(-1);
}
pnew->data = in_data;
pnew->next = NULL;
//执行插入操作
if(my_queue->front==NULL && my_queue->rear==NULL){
//说明是第一个数据节点
my_queue->front = pnew;
my_queue->rear = pnew;
}else{
//说明不是第一个数据节点
my_queue->rear->next = pnew;
my_queue->rear = pnew;
}
return 0;
}
//遍历队列
int print_queue(queue_t *my_queue){
if(NULL == my_queue){
printf("入参为NULL, 请检查\n");
return -1;
}
node_t *ptemp = my_queue->front;
while(NULL != ptemp){
printf("%d ", ptemp->data);
ptemp = ptemp->next;
}
printf("\n");
return 0;
}
//数据出队列
int pop_queue(queue_t *my_queue, int *out_data){
if(NULL == my_queue || NULL == out_data){
printf("入参为NULL, 请检查\n");
return -1;
}
if(is_empty(my_queue)){
printf("队列已经空了 出队列失败\n");
return -1;
}
//执行出队列的操作
*out_data = my_queue->front->data;
node_t *pdel = my_queue->front;
my_queue->front = pdel->next;
free(pdel);
pdel = NULL;
//如果出队列之后没有节点了 rear的指向也要更新
if(NULL == my_queue->front){
my_queue->rear = NULL;
}
return 0;
}
//判断队列是否为空 返回1空 0非空
int is_empty(queue_t *my_queue){
if(NULL == my_queue){
printf("入参为NULL, 请检查\n");
return -1;
}
return my_queue->front==NULL?1:0;
}
//清空队列
int clean_queue(queue_t *my_queue){
if(NULL == my_queue){
printf("入参为NULL, 请检查\n");
return -1;
}
//头删法删除所有节点
node_t *pdel = NULL;
while(NULL != my_queue->front){
pdel = my_queue->front;
my_queue->front = pdel->next;
free(pdel);
}
pdel = NULL;
my_queue->rear = NULL;
return 0;
}
//销毁队列
int destroy_queue(queue_t **my_queue){
if(NULL == my_queue || NULL == *my_queue){
printf("入参为NULL, 请检查\n");
return -1;
}
//先调用清空
clean_queue(*my_queue);
//再销毁
free(*my_queue);
*my_queue = NULL;
return 0;
}
main.c
#include "link_queue.h"
int main(){
queue_t *my_queue = NULL;
//创建队列
create_queue(&my_queue);
//数据入队列
push_queue(my_queue, 10);
push_queue(my_queue, 20);
//printf("front = %p rear = %p\n", my_queue->front, my_queue->rear);
//遍历队列
print_queue(my_queue);
//数据出队列
int num = 0;
pop_queue(my_queue, &num);
printf("num = %d\n", num);
//printf("front = %p rear = %p\n", my_queue->front, my_queue->rear);
pop_queue(my_queue, &num);
printf("num = %d\n", num);
//pop_queue(my_queue, &num);//失败 队列已经空了
//printf("num = %d\n", num);
//printf("front = %p rear = %p\n", my_queue->front, my_queue->rear);
//再入队列几个元素
push_queue(my_queue, 10);
push_queue(my_queue, 20);
push_queue(my_queue, 30);
push_queue(my_queue, 40);
//遍历队列
print_queue(my_queue);
//清空队列
clean_queue(my_queue);
//遍历队列
print_queue(my_queue);
//销毁队列
destroy_queue(&my_queue);
printf("my_queue = %p\n", my_queue);
return 0;
}
1.11递归
1.11.1概念
递归就是在函数内部再次调用函数自身的逻辑。
注意,每个递归都要有一个出口,否则函数无限次的调用下去,会把内存资源耗尽,
会出现堆栈溢出,导致段错误。
注意,递归本质就是函数调用,不是C语言程序结构的一种。
C语言程序结构只有三种:顺序结构、分支结构(选择结构)、循环结构。
1.11.2例子
1~n求和
#include <stdio.h>
//1~n求和
int func(int n){
if(n==1){
return 1;//递归的出口
}
return n+func(n-1);
}
int main(int argc, const char *argv[])
{
int ret = 0;
ret = func(5);
printf("sum = %d\n", ret);
return 0;
}
青蛙跳台阶,共有10阶台阶,青蛙每次可以选择跳一阶或者两阶,
问:青蛙跳上这10个台阶共有多少种跳法
#include <stdio.h>
//计算跳上第n阶 有几种跳法
//返回值:就是跳法的多少
int func(int n){
//递归的出口
if(1 == n){
return 1;
}
if(2 == n){
return 2;
}
return func(n-1) + func(n-2);
}
int main(int argc, const char *argv[])
{
int ret = 0;
ret = func(10);
printf("ret = %d\n", ret);//89
return 0;
}
1.12.1快速排序
1.12.1.1概念
快速排序是冒泡排序的优化,也是一种基于交换的排序方式。
时间复杂度 O(nlogn)。
1.12.1.2基本思想
分而治之
通过一趟排序,先将数据分成两部分,其中一部分的数据,
都比另一部分的数据大(小),(每部分数据内部不要求有序)
然后,对于两部分数据分别进行上述的排序操作,把没部分数据再分成两部分,
依次类推,直到整个数据有序。
1.12.1.3代码实现
#include <stdio.h>
//一趟排序
int sort(int *s, int low, int high){
//选取一个比较的基准,选待排序列中的那个都可以
//一般情况下,为了方便写代码,我们都选下标最小的那个
int flag = s[low];
while(low<high){
while(s[high] > flag && low < high){//如果是降序,将此处的s[high] > flag 改成 s[high] < flag
high--;
}
if(low<high){
s[low] = s[high];
low++;
}
while(s[low] < flag && low < high){//如果是降序,将此处的s[low] < flag 改成 s[low] > flag
low++;
}
if(low<high){
s[high] = s[low];
high--;
}
}
//记得把 flag 塞回去
s[low] = flag;
return low;//return high也可以,因为此时 low和high是相等的
}
//快速排序
int quick_sort(int *s, int low, int high){
//递归的出口
if(low<high){
int ret = sort(s, low, high);
quick_sort(s, 0, ret-1);
quick_sort(s, ret+1, high);
}
}
int print_arr(int *p){
int i = 0;
for(i = 0; i < 10; i++){
printf("%d ", p[i]);
}
printf("\n");
}
int main(int argc, const char *argv[])
{
int s[10] = {6, 4, 9, 3, 7, 5, 8, 2, 1, 10};
//排序前
print_arr(s);
//执行快速排序
quick_sort(s, 0, 9);
printf("---------------------------\n");
//排序后
print_arr(s);
return 0;
}
1.14树的前中后续遍历
1.14.1概念
前序遍历:根左右,先遍历根节点,然后遍历左子树,最后遍历右子树。
一般在构建树的过程中会用到前序遍历,因为现有根,才有左右子树。
中序遍历:左根右,先遍历左子树,然后遍历根节点,最后遍历右子树。
一般遍历时使用中序,对于有序的树,使用中序遍历能得到有序的序列。
后序遍历:左右根,先遍历左子树,然后遍历右子树,最后遍历根节点。
释放树中所有节点时,一般使用后续,因为如果把根节点释放了,就没法访问左右子树了。
1.14.2图例
前序:ABCDEFGHI
中序:BDCAEHGIF
后序:DCBHIGFEA
1.14.3代码实现
#include <stdio.h>
#include <stdlib.h>
//树的节点类型
typedef struct _Node{
int data;//数据域
struct _Node *lchild;//指向左孩子的指针
struct _Node *rchild;//指向右孩子的指针
}node_t;
//创建一棵树
int create_tree(node_t **root){
if(NULL == root){
printf("入参为NULL, 请检查\n");
return -1;
}
char value = 0;
scanf("%c", &value);
getchar();//清理垃圾字符
//递归的出口
if('#' == value){
return 0;
}
//分配空间
*root = (node_t *)malloc(sizeof(node_t));
if(NULL == *root){
printf("内存分配失败\n");
exit(-1);
}
(*root)->data = value;
(*root)->lchild = NULL;
(*root)->rchild = NULL;
//创建左子树
create_tree(&((*root)->lchild));
//创建右子树
create_tree(&((*root)->rchild));
return 0;
}
//前序遍历
int qianxu(node_t *root){
if(NULL == root){//递归的出口
return -1;
}
printf("%c", root->data);
qianxu(root->lchild);
qianxu(root->rchild);
return 0;
}
//中序遍历
int zhongxu(node_t *root){
if(NULL == root){//递归的出口
return -1;
}
zhongxu(root->lchild);
printf("%c", root->data);
zhongxu(root->rchild);
return 0;
}
//后序遍历
int houxu(node_t *root){
if(NULL == root){//递归的出口
return -1;
}
houxu(root->lchild);
houxu(root->rchild);
printf("%c", root->data);
return 0;
}
//销毁树
int destroy_tree(node_t **root){
if(root == NULL || *root == NULL){
return -1;
}
destroy_tree(&((*root)->lchild));
destroy_tree(&((*root)->rchild));
free(*root);
*root = NULL;
return 0;
}
int main(){
node_t *root = NULL;
create_tree(&root);
//前序
printf("前序遍历\n");
qianxu(root);
printf("\n");
//中序
printf("中序遍历\n");
zhongxu(root);
printf("\n");
//后序
printf("后序遍历\n");
houxu(root);
printf("\n");
//销毁树
destroy_tree(&root);
return 0;
}
1.14.4树的练习
练习1:
已知一棵树前序和中序的遍历结果:
前序: A B C E H F I J D G K
中序: A H E C I F J B D K G
请画出这个树的结构。
解题思路:
根据前序,能确定根,
然后根据中序,确定哪些节点是根的左子树,哪些是根的右子树
然后再根据前序确定子树的根
在根据中序确定子树的子树。。。依次类推。。
1.15哈希
练习:
从终端输入任意一个只包含小写字母的字符串
输出每个字符出现的次数。
如:输入:aabbccdfe
#include <stdio.h>
int get_index(char value){
return value-'a';
}
int main(){
char s[128] = {0};
printf("请输入字符串(小写):");
scanf("%s", s);
//用来保存字母出现次数的数组
//count[0] --> a出现的次数
//count[1] --> b出现的次数
//...
//count[25] --> z出现的次数
unsigned int count[26] = {0};
//遍历字符串,统计次数
int i = 0;
int index = 0;
while(s[i] != '\0'){
index = get_index(s[i]);
count[index]++;
i++;
}
//输出次数
for(i = 0; i < 26; i++){
printf("%c : %d\n", i+'a', count[i]);
}
return 0;
}