C++基础入门【3】- 数据结构与算法
绪论
GT切换界面 list.c和list.h
数据 - 是描述客观事物的符号, 是计算机可以操作的对象
结构 - 关系
在计算机中, 数据并不是孤立的,无序的,而是存在一定的关系.
数据存储 :
int age = 19;
char* name = "james";
int a[] = {1, 2, 3, 4, 5};
char* name[] = {
"maji", "madong", "houyaowen", "guodegang", "yueyunpeng"};
存储数据 - 名字 - 没问题
缺点 - 没有存储数据之间的关系
变量 数组 结构体 malloc - 记录/存储 数据
数据之间的关系 - no
树结构
数据结构 :
1.如何存储数据
2.存储数据之间的关系
|
使用这些数据
算法 :
程序 = 数据结构 + 算法
算法 - 如何处理信息
五个特点:
有穷性
确定性
可行性
输入
输出
目标 - 好的算法
正确性
可读性
健壮性
高效率和低存储
高效率 - 执行速度快
低存储 - 内存占用小
查找算法 - 搜索引擎 高性能数据库
排序算法 - Excel 任务调度 搜索引擎结果显示
高效率 - 时间 - 时间复杂度
时间开销 - 事后统计
机器性能
编程语言
编译的机器
无法事后统计
预先估计 - 时间复杂度
实际预估时间开销 - T
问题规模 - n
void go_home(int n){
int i = 1;
while(i <= n){
printf("go step : %d\n", i)
}
printf("go step more than %d\n", i)
}
int main(void){
go_home(10000);
}
语句频度:
int i = 1; ------------------------------>1次
while(i <= n){---------------------------->10001次
i++;---------------------------------->10000次
printf("go step : %d\n", i)----------->10000次
}
printf("go step more than %d\n", i)------->1次
T(10000) = 1 + 10001 + 10000*2 + 1 = 3 * 10000 + 3
T(n) = 3n + 3
简化时间复杂度 -
当n的值为足够大的时候, 省略比较低阶的部分, 只保留最高阶的部分
T(n) = 3n
T(n) = n
O(n)
T(n) = n² + n + 1
T(1) = 1 + 1 + 1 = 3
T(10000) = 100000000 + 1000 + 1
= 100010001
O(n²)
很多代码
顺序执行 - 可以忽略
循环执行
多层循环 - 最内层循环
===============================
研究什么问题 -
逻辑结构 - 数据和数据之间的关系
集合结构
集合中的数据,同属于一个集合,没有任何其他关系
线性结构
线性结构中的数据元素之间是一对一的先后关系
树形结构
一对多的关系
图形结构
多对多的关系
存储结构/物理结构 - 数据在计算机中的存储形式
顺序存储结构
数据元素放在连续的存储单元里
逻辑上相连的元素在物理上也是相连的
链式存储结构
逻辑上相邻的数据元素在物理上可以不相邻
借助于元素存储的指针来表示元素之间的逻辑关系
吃饭排队拿号 - 银行办业务
顺序 - 链式
顺 - 物理上是连续的
链 - 非顺序, 可以是离散的
影响存储空间分配的方便程度 - 链式
影响数据运算的速度 - 顺序
索引存储结构
在存储元素信息的同时, 建立附加的索引表
散列存储结构 - 哈希存储
根据元素的关键字直接计算出该元素的存储地址
数据运算
运算的定义 - 针对逻辑结构
运算的实现 - 针对物理结构
12.栈 - 先进后出
数据结构研究对象:
栈 队列 单链表 双链表 二叉树
栈 -
使用只能在一端进行插入和删除操作的特殊线性表
按照后进先出的原则存储数据
先进入的数据被压入栈底, 最后的数据在栈顶
需要读取数据的时候从栈顶开始读取数据
先进后出 后进先出
内存是连续的
以数组的方式来存储数据 - 具有随机访问的能力
将数据保存到栈的内存中的过程 - 入栈 - 压栈 - push stack
将数据从栈中获取的过程 - 出栈 - 弹栈 - pop stack
声明一个栈的数据结构:
struct stack{
int* arr;//作为栈的首地址
int cap;//作为栈的容量
int top;//栈顶
};
分析:
1.栈的成员
arr - 栈的首地址
cap
2.将数据放入到栈的哪个位置, 从栈的哪个位置上获取数据
需要有一个标识- 标识可以将数据放到哪里 - 标识可以从哪里获取数据
top - 标识/记录 读写的位置 - 下标
栈顶
在栈中没有任何的数据 此时栈顶的位置应该为多少 - 0
3.初始化
int* arr; - 有没有分配存储区来存储数据
- 仅仅是一个首地址
- 分配栈的存储
cap
top - 没有任何的数据 - 将top初始化为0 - 放入到下标为0的位置上
free(stack)
代码:
stack.h
stack.c
main.c
存储区 + 操作函数
tarena@TNV:stack$ cat Makefile
#类似于C语言里面的宏
OBJ = main.o stack.o
BIN = stack
cc = gcc
RM = rm
#编译规则1
$(BIN):$(OBJ)
$(CC) $(OBJ) -o $(BIN)
#编译规则2:让.c文件生成对应的.o文件
%.o:%.c
$(CC) -c $< -o $@
#伪目标
clean:
$(RM) $(OBJ) $(BIN)
//stack.h
#ifndef _STACK_H
#define _STACK_H
//包含的公共头文件
#include <stdio.h>
#include <stdlib.h>
//声明描述栈的属性的结构体
typedef struct stack{
int* arr;//作为栈的首地址
int cap;//作为栈的容量
int top;//栈顶
}stack_t;
//声明栈的操作函数
extern void stack_init(stack_t* stack,int cap);//初始化
extern void stack_deinit(stack_t* stack);//栈的内存释放
extern int stack_full(stack_t* stack);//判断栈是否为满
extern int stack_empty(stack_t* stack);//判断栈是否为空
extern void stack_push(stack_t* stack,int data);//定义入栈函数
extern int stack_pop(stack_t* stack);//定义出栈函数
extern int stack_size(stack_t* stack);//判断栈中有效数据元素个数的函数
#endif
//stack.c
#include "stack.h"
//栈的初始化
void stack_init(stack_t* stack,int cap){
stack->arr = malloc(sizeof(int)*cap);
//初始化容量
stack->cap = cap;
//初始化top
stack->top = 0;
}
//栈的内存释放
void stack_deinit(stack_t* stack){
free(stack->arr);//释放内存
stack->cap = 0;
stack->top = 0;
}
//判断栈是否为满
int stack_full(stack_t* stack){
//满 , 1
//非满, 0
return stack->top == stack->cap;
}
//判断栈是否为空
int stack_empty(stack_t* stack){
//空,1
//非空,0
return stack->top == 0;
}
//定义入栈函数
void stack_push(stack_t* stack,int data){
//将数据data放到下标为top的位置
stack->arr[stack->top] = data;
stack->top++;
}
//定义出栈函数
int stack_pop(stack_t* stack){
stack->top--;
return stack->arr[stack->top];
}
//判断栈中有效数据元素个数的函数
int stack_size(stack_t* stack){
return stack->top;
}
//main.c
#include "stack.h"
int main(void){
//定义栈的变量
stack_t stack;
//栈的初始化
stack_init(&stack, 20);
int data = 520;
while(!stack_full(&stack))//压栈
stack_push(&stack,data++);
printf("栈中的有效数据个数是:%d个\n", stack_size(&stack));
printf("栈中的数据为:");
//循环出栈
while(!stack_empty(&stack))//非空
printf("%d ", stack_pop(&stack));
printf("\n");
printf("栈中的有效数据个数是:%d个\n", stack_size(&stack));
//栈的内存释放
stack_deinit(&stack);
return 0;
}
tarena@TNV:stack$ ls
main.c Makefile stack.c stack.h
tarena@TNV:stack$ make
cc -c main.c -o main.o
cc -c stack.c -o stack.o
cc main.o stack.o -o stack
tarena@TNV:stack$ ls
main.c main.o Makefile stack stack.c stack.h stack.o
tarena@TNV:stack$ ./stack
栈中的有效数据个数是:20个
栈中的数据为:539 538 537 536 535 534 533 532 531 530 529 528 527 526 525 524 523 522 521 520
栈中的有效数据个数是:0个
栈 - 先进后出
栈应用场景
一切满足后进先出的场景, 都可以通过栈来维护
浏览器的页面的前进后退缓存
word, Excel - 撤销操作
单道停车场问题
13.队列 - 先进先出 - first in first out - FIFO - 缓冲区
队列 - 先进先出 - first in first out - FIFO - 缓冲区
特点:具有先进先出的特性
typedef queue{
int* arr;//首地址
int cap;//容量
int size;//队列中有效数据的个数
int front;//出队的位置, 队首
int rear; //入队的位置, 队尾
}queue_t;
将数据保存到队列的内存中, 入队
将数据从队列中内存中获取, 出队
入队和出列的位置是不是同一个位置
将数据1,2放入队列中
1 -> 下标0
2 -> 下标1
1.从队列中获取一个数据 - 获取的是哪个数据 - 获取的是1, 从下标为0的位置上获取
先进先出的原则
2.把3放入队列中 - 放到哪个位置上 -
是否将新的数据3放到下标为0的位置上 - 不对
放到下标为2的位置上
入队 - 位置 - 2
出队 - 位置 - 0
-> 初始化
arr - malloc(sizeof(int)*cap)
cap - cap
size - 没有有效数据 - 0
-----------------
front - 出对的位置
rear - 入队的位置
初始化 - 不知道front等于多少 - ?
将第一个数据放到下标为0的位置上 -
获取数据 - 从哪个位置获取数据 - 0
参照 - 栈的初始化和释放
栈 - top 和 cap的关系 - 判断栈是否为满, 是否为空
if(size == cap)
满
else
非满
if(size == 0)
空
else
非空
mkdir queue
queue.h
queue.c
main.c
出队 出栈 - 将数据获取走
数据还在队列, 栈中 - 认为 - 无效化
while(!queue_full(&queue)){
queue_push(&queue, 100);
}
出队 - front改变
入队 - rear改变
//queue.h
#ifndef _QUEUE_H
#define _QUEUE_H
//包含公共的头文件
#include <stdio.h>
#include <stdlib.h>
//声明描述队列的数据结构
typedef struct queue{
int* arr;//首地址
int cap;//容量
int size;//队列中有效数据的个数
int front;//出队的位置,队首
int rear;//入队的位置,队尾
}queue_t;
//声明操作函数
extern void queue_init(queue_t* queue, int cap);//初始化
extern void queue_deinit(queue_t* queue);//释放内存
extern int queue_full(queue_t* queue);//判断满,满,返回1;非满,返回0;
extern int queue_empty(queue_t* queue);//判断空,空,返回1;非空,返回0
extern void queue_push(queue_t* queue, int data);//入队
extern int queue_pop(queue_t* queue);//出队
extern int queue_size(queue_t* queue);//返回有效数据个数的函数
#endif
//queue.c
#include "queue.h"
//初始化
void queue_init(queue_t* queue, int cap){
queue->arr = malloc(sizeof(int) * cap);
//初始化容量
queue->cap = cap;
queue->size = 0;
queue->front = 0;
queue->rear = 0;
}
//释放内存
void queue_deinit(queue_t* queue){
free(queue->arr);//释放内存
queue-> cap = 0;
queue->size = 0;
queue->front = 0;
queue->rear = 0;
}
//判断满
int queue_full(queue_t* queue){
return queue->size == queue->cap;
}
//判断空
int queue_empty(queue_t* queue){
return 0 == queue->size;
}
//入队
void queue_push(queue_t* queue, int data){
queue->size++;
if(queue->rear == queue->cap)
queue->rear = 0;//构造循环队列
queue->arr[queue->rear++] = data;
}
//出队
int queue_pop(queue_t* queue){
if(queue->front == queue->cap)
queue->front = 0;//构造循环队列
queue->size--;//更新计数
return queue->arr[queue->front++];
}
//定义返回有效数据个数的函数
int queue_size(queue_t* queue){
return queue->size;
}
//main.c
#include "queue.h"
int main(void){
queue_t queue;
//初始化
queue_init(&queue, 4);
//入队
for(int i = 10; i <= 40; i += 10){
if(!queue_full(&queue))
queue_push(&queue, i);//入队:10 20 30 40
}
printf("队列中有效数据个数为:%d\n", queue_size(&queue));
//出队两个数据 - 10(0),20(1)
for(int i = 0; i < 2; i++){
if(!queue_empty(&queue))
printf("%d ", queue_pop(&queue));
}
printf("\n");
printf("队列中有效数据个数为:%d\n", queue_size(&queue));
//入队 50 60
//50(0) 60(1) 30(2) 40(3)
for(int i = 50; i <= 60; i += 10){
if(!queue_full(&queue))
queue_push(&queue, i);//入队:50 60
}
//出队
while(!queue_empty(&queue)){
printf("%d ", queue_pop(&queue));//30 40 50 60 -- 出队顺序
}
printf("\n");
//释放内存
queue_deinit(&queue);
return 0;
}
tarena@TNV:queue$ ls
main.c queue queue.c queue.h
tarena@TNV:queue$ gcc main.c queue.c -o queue
tarena@TNV:queue$ ./queue
队列中有效数据个数为:4
10 20
队列中有效数据个数为:2
30 40 50 60
14.单链表
思考题目:
声明描述学生信息的结构体, 定义三个结构体变量来描述三个学生信息
要求:
可以通过第一个学生找到第二个学生,
可以通过第二个学生找到第三个学生.
student.c
1. s1, s2, s3 - 三个结构体类型变量 - 内存之间并不连续
2. s1, s2, s3 - 三个结构体类型变量 - 元素之间具有先后关系
链式存储结构
常用的处理措施 -
给链式物理结构的元素 - 添加两个新的元素
头结点 - 表示链式物理结构的起始 - 头结点的下一个结点 - 第一个有效结点
尾结点 - 表示链式物理结构的结束 - 最后一个有效结点的下一个结点 - 尾节点
头结点和尾节点 不存储任何的有效数据
不需要头结点和尾结点 - 可以不需要
//利用head和tail来优化遍历打印每个学生姓名
for(stu_t* pnode = &head; pnode != &tail; pnode = pnode->next){
stu_t* pmid = pnode->next;
if(pmid != &tail)
printf("%s:%d\n", pmid->name, pmid->age);
}
pnode变化范围
起始 - head(头结点)
结束 - 最后一个有效结点
pmid永远是pnode的下一个结点
变化范围
起始 - 第一个有效结点
结束 - tail(尾结点)
上述代码是一个单链表吗 - 不算 - 链式存储结构
内存 + 解决方法 - 数据结构
顺序结构 - 链式结构
顺序 - 缺点 - 插入和删除的时候需要移动大量元素, 耗费大量时间
- 有点 - 随机访问能力 - 数组下标
链式结构 - 插入 删除 简单
- 内存之间并不连续
- 从前一个数据得到后一个数据 - 耗费大量时间
单链表 - 每个节点内存不一定连续, 通过一个元素可以找到下一个元素, 朝一个方向操作
描述结点的结构体类型:
struct node{
结点数据信息;//年龄, 姓名, 结构体, ....
struct node* next;//保存下一个结点首地址
};//将来根据自己的需求将node名字修改为自己想要的名字, 例如:student, employee, ...
描述整个单链表的结构体类型:
struct list{
struct node* head;//保存头结点的首地址
struct node* tail;//保存尾结点的首地址
};
刚刚构造了一个单链表 - 只有一个头结点和一个尾结点 - 没有任何的有效结点
分析:
1.真实存在的头结点和尾结点
struct node* head;
struct node* tail;
两个指针 - 头结点和尾结点的元素 - 不存在
2.插入新的结点
pfirst - 要插入的前一个结点
pmid - 指向要插入的后一个结点
升级:
1.头插函数
新的结点插入到头结点和第一个有效结点之间
void list_add_head(list_t* list, int data){}
2.尾插函数
新的结点插入到最后一个有效结点和尾结点之间
void list_add_tail(list_t* list, int data){}
list_add
mkdir list
vim list.h
vim list.c
vim main.c
#include <stdio.h>
//声明描述学生信息的结构体
typedef struct student{
char name[32];
int age;
struct student* next;//保存下一个学生信息的首地址
}stu_t;
int main(void){
//定义初始化三个学生的信息
stu_t s1 = {.name = "贾宝玉", .age = 13, .next = NULL};
stu_t s2 = {.name = "林黛玉", .age = 25, .next = NULL};
stu_t s3 = {.name = "薛宝杈", .age = 34, .next = NULL};
stu_t head;
stu_t tail;
//s1存放s2的地址,s2存放s3的地址
head.next = &s1;
s1.next = &s2;
s2.next = &s3;
s3.next = &tail;
tail.next = NULL;
//访问结构体中的成员
printf("%s, %s, %s\n", s1.name, s2.name, s3.name);
//通过第一个学生,打印第二个学生的信息,通过第二个学生打印第三个学生的信息
//1.next太长了
//2.如果s1没有了,s1修改为s2,维护起来非常麻烦
//s1.next = &s2;
//s1.next->next = &s3;
printf("%s, %s, %s\n", s1.name, s1.next->name, s1.next->next->name);
//利用head和tail来优化遍历打印每个学生姓名
for(stu_t* pnode = &head; pnode != &tail; pnode = pnode->next){
stu_t* pmid = pnode->next;
if(pmid != &tail){
printf("%s:%d\n", pmid->name, pmid->age);
}
}
return 0;
}
tarena@TNV:day25$ vim student.c
tarena@TNV:day25$ gcc student.c -o student
tarena@TNV:day25$ ./student
贾宝玉, 林黛玉, 薛宝杈
贾宝玉, 林黛玉, 薛宝杈
贾宝玉:13
林黛玉:25
薛宝杈:34
1.头插函数
新的结点插入到头结点和第一个有效结点之间
void list_add_head(list_t* list, int data){}
2.尾插函数
新的结点插入到最后一个有效结点和尾结点之间
void list_add_tail(list_t* list, int data){}
head---->10--->20---->30---->tail
//list.h
#ifndef _LIST_H
#define _LIST_H
//包含公共的头文件
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
//声明描述结点信息的结构体类型
typedef struct node{
int data;//结点数据
struct node* next;//保存下一个结点的首地址
}node_t;
//声明描述单链表的结构体类型
typedef struct list{
struct node* head;//保存头结点的首地址
struct node* tail;//保存尾结点的首地址
}list_t;
//声明单链表的操作函数
extern void list_init(list_t* list);//初始化
extern void list_travel(const list_t* list);//遍历
extern void list_deinit(list_t* list);//释放
extern bool list_add(list_t* list, int data);//插入
extern void list_add_head(list_t* list, int data);//头插法
extern void list_add_tail(list_t* list, int data);//尾插法
extern bool list_del(list_t* list, int data);//删除
#endif
//list.c
#include "list.h"
//定义分配新结点并且初始化新结点的函数
static node_t* create_node(int data){
//分配结点内存
node_t* pnew = (node_t *)malloc(sizeof(node_t));
//初始化结点内存
pnew->data = data;
pnew->next = NULL;
//返回新的结点内存的首地址
return pnew;
}
//单链表初始化
void list_init(list_t* list){
//给头结点分配内存
list->head = create_node(0);
//给尾结点分配内存
list->tail = create_node(0);
//首尾相连
list->head->next = list->tail;
list->tail->next = NULL;
}
//定义单链表的遍历函数
void list_travel(const list_t* list){
for(node_t* pnode = list->head; pnode != list->tail; pnode = pnode->next){
//1.定义3个游标
node_t* pfirst = pnode;
node_t* pmid = pfirst->next;
node_t* plast = pmid->next;
//2.判断pmid
if(pmid != list->tail)
printf("%d ", pmid->data);
}
printf("\n");
}
//定义顺序插入函数
bool list_add(list_t* list, int data){
//1.创建新的结点
node_t* pnew = create_node(data);
//2.遍历查找要插入的位置
for(node_t* pnode = list->head; pnode != list->tail; pnode = pnode->next){
//1.创建3个游标
node_t* pfirst = pnode;
node_t* pmid = pfirst->next;
node_t* plast = pmid->next;
//2.找到要插入到的位置,插入到pfirst和pmid之间
//head---->10---->20-------->30---->tail
// pfirst pmid
// pnew(25)
//head---->10---->20-------->30------------>tail
// pfirst pmid
// pnew(40)
if(pmid->data >= pnew->data || pmid == list->tail/*新的结点数据在整个链表最大*/){
pfirst->next = pnew;
pnew->next = pmid;
//break;
return true;
}
}
return false;
}
//定义头插函数-新的结点插入到头结点和第一个有效结点之间
void list_add_head(list_t* list, int data){
//构造新的结点
node_t* pnew = create_node(data);
//将新的结点插入到头结点和第一个有效结点之间
//head--------------->10------->20------>tail
// pnew
//head
node_t* ptmp = list->head->next;//临时备份原先的第一个结点
list->head->next = pnew;
pnew->next = ptmp;
}
//定义尾插函数 - 新的jeidian插入到最后一个有效结点和尾结点之间
void list_add_tail(list_t* list, int data){
//head--------->10---------->20-------------->tail
// pnew
// pfirst pmid
//构造新的结点
node_t* pnew = create_node(data);
for(node_t* pnode = list->head; pnode != list->tail; pnode = pnode->next){
//定义3个游标
node_t* pfirst = pnode;
node_t* pmid = pfirst->next;
node_t* plast = pmid->next;
if(pmid == list->tail){//让pmid指向尾结点,此时pfirst指向原先的最后一个结点
pfirst->next = pnew;
pnew->next = pmid;
}
}
}
//删除某个结点的函数
bool list_del(list_t* list, int data){
//head---->10---->20-------->30---->tail
// pfirst pmid plast
//遍历要删除的结点,并且让pmid指向要删除的结点
for(node_t* pnode = list->head; pnode != list->tail; pnode = pnode->next){
//1.创建3个游标
node_t* pfirst = pnode;
node_t* pmid = pfirst->next;
node_t* plast = pmid->next;
//2.遍历找到要删除的位置
if(data == pmid->data && pmid != list->tail){//不能将尾结点删除
pfirst->next = pmid->next;//连接前一个结点和后一个结点
free(pmid);//释放要删除的结点内存
//break;
return true;
}
}
return false;
}
//定义释放链表所有结点的函数
void list_deinit(list_t* list){
node_t* pnode = list->head;
while(pnode){
node_t* ptmp = pnode->next;//临时分配下一个要删除的结点
free(pnode);//释放结点内存
pnode = ptmp;//准备释放掉下一个结点
}
}
//mian.c
#include "list.h"
int main(void){
//创建单链表
list_t list;
list_init(&list);
//各种插入和遍历
bool ret = false;
int num = 0;
printf("请输入要插入到单链表的数:");
while(true){
scanf("%d", &num);
if(0 == num){
printf("停止插入操作\n");
break;
}
ret = list_add(&list, num);
if(ret){
printf("插入元素%d成功\n", num);
}else{
printf("插入元素%d失败\n", num);
}
}
list_add_head(&list, 30);
list_add_head(&list, 30);
list_add_tail(&list, 6);
list_add_tail(&list, 6);
//遍历单链表
list_travel(&list);
//删除
while(true){
printf("请输入要删除的元素:");
scanf("%d", &num);
if(0 == num){
printf("停止删除操作\n");
break;
}
ret = list_del(&list, num);
if(ret){
printf("删除元素%d成功\n", num);
}else{
printf("该元素%d不在单链表中\n", num);
}
//遍历单链表
list_travel(&list);
}
//释放单链表
list_deinit(&list);
return 0;
tarena@TNV:list$ vim main.c
tarena@TNV:list$ gcc main.c list.c -o list
tarena@TNV:list$ ./list
请输入要插入到单链表的数:10 50 25 36 66 88 34 46 98 0
插入元素10成功
插入元素50成功
插入元素25成功
插入元素36成功
插入元素66成功
插入元素88成功
插入元素34成功
插入元素46成功
插入元素98成功
停止插入操作
30 30 10 25 34 36 46 50 66 88 98 6 6
请输入要删除的元素:25
删除元素25成功
30 30 10 34 36 46 50 66 88 98 6 6
请输入要删除的元素:11
该元素11不在单链表中
30 30 10 34 36 46 50 66 88 98 6 6
请输入要删除的元素:0
停止删除操作
15.双链表
使用.
变量 .
使用->
指针 ->
双链表:
问题 - 可以通过tail找到30数据所在节点 - 不可以
通过前一个节点找到后一个节点
无法通过后一个节点找到前一个节点
为了克服这个问题 - 在设计链表的时候, 可以在每个节点中再设置一个指向前面结点的指针
每个结点中都包含两个指针
一个指向后面的元素
一个指向前面的元素
双向链表
声明描述一个节点的结构体
struct node{
int data;
struct node* next;//指向下一个节点的首地址
struct node* prev;//指向上一个节点的首地址
};
声明描述一个双链表的结构体
struct list{
struct node head;
struct node tail;
};
mkdir list2
vim list.h
vim list.c
vim main.c
pmid指针
循环的次数 - 有效数据的个数 + 1
循环 - pmid != &list->tail
循环次数 == 有效数据个数
typedef struct birth{
int year;
int month;
int day;
}birth_t;
typedef struct info{
int num;
char name[32];
int num2;
char sex;
birth_t birth;
char add[40];
}info_t;
typedef struct node{
info_t info;
struct node* next;
struct node* prev;
}node_t;
list.h
//list.h
#ifndef _LIST_H
#define _LIST_H
//包含公共的头文件
#include <stdio.h>
#include <stdlib.h>
//声明描述结点的结构体
typedef struct node{
int data;
struct node* next;//保存下一个结点首地址
struct node* prev;//保存上一个结点首地址
}node_t;
//声明描述双链表的结构体
typedef struct list{
struct node head;
struct node tail;
}list_t;
extern void list_init(list_t* list);//初始化函数
extern int list_empty(list_t* list);//判断链表是否为空的函数
extern int list_size(list_t* list);//结点个数
extern void list_add(list_t* list, int data);//按照顺序插入
extern void list_add_head(list_t* list, int data);//头插
extern void list_add_tail(list_t* list, int data);//尾插
extern void list_del(list_t* list,int data);//删除指定结点
extern void list_del_head(list_t* list);//头删
extern void list_del_tail(list_t* list);//尾删
extern int list_get(list_t* list, int index);//根据结点编号获取数据
extern void list_deinit(list_t* list);//释放内存
extern void list_printf(list_t* list);//打印
#endif
list.c
//list.c
#include "list.h"
//初始化函数
void list_init(list_t* list){
//初始化头结点
list->head.next = &list->tail;
list->head.prev = NULL;
list->head.data = 0;
//初始化尾结点
list->tail.next = NULL;
list->tail.prev = &list->head;
list->tail.data = 0;
}
//判断空函数
int list_empty(list_t* list){
//如果链表中只有头结点和尾结点
return list->head.next == &list->tail;//满足,空;不满足,非空
}
//判断结点个数函数
int list_size(list_t* list){
int count = 0;//记录结点个数
for(node_t* pnode = &list->head; pnode != &list->tail; pnode = pnode->next){
//制造三个游标
node_t* pfirst = pnode;
node_t* pmid = pfirst->next;
node_t* plast = pmid->next;
if(pmid != &list->tail){
count++;
}
}
return count;
}
//定义构造新结点的函数
static node_t* create_node(int data){
node_t* pnew = malloc(sizeof(node_t));
pnew->data = data;
pnew->next = NULL;
pnew->prev = NULL;
return pnew;
}
static void insert_node(node_t* pfirst, node_t* pmid, node_t* pnew){
pfirst->next = pnew;
pnew->prev = pfirst;
pnew->next = pmid;
pmid->prev = pnew;
}
//按照顺序插入函数
void list_add(list_t* list, int data){
node_t* pnew = create_node(data);
//head<-------->10<-------->20<---------->30<---------->tail
// pnew(15)
// pfirst pmid
for(node_t* pnode = &list->head; pnode != &list->tail; pnode = pnode->next){
//制造3个游标
node_t* pfirst = pnode;
node_t* pmid = pfirst->next;
node_t* plast = pmid->next;
if(pnew->data <= pmid->data || pmid == &list->tail){
insert_node(pfirst, pmid, pnew);
break;
}
}
}
//头插入函数
void list_add_head(list_t* list, int data){
node_t* pnew = create_node(data);
//head<-------->10<-------->20<---------->30<---------->tail
// pnew(15)
//pfirst pmid
node_t* pfirst = &list->head;
node_t* pmid = pfirst->next;
node_t* plast = pmid->next;
//将pnew结点插入到pfirst和pmid之间
insert_node(pfirst, pmid, pnew);
}
//尾插函数
void list_add_tail(list_t* list, int data){
node_t* pnew = create_node(data);
//head<-------->10<-------->20<---------->30<---------->tail
// pnew
// pfirst pmid
//制造游标
node_t* pfirst = list->tail.prev;
node_t* pmid = pfirst->next;
node_t* plast = pmid->next;
//将pnew插入到pfirst和pmid之间
insert_node(pfirst, pmid, pnew);
}
//定义删除pmid指向的结点
static void del_node(node_t* pfirst, node_t* pmid, node_t* plast){
pfirst->next = plast;
plast->prev = pfirst;
free(pmid);
}
//删除指定的结点
void list_del(list_t* list, int data){
//head<-------->10<-------->20<---------->30<---------->tail
for(node_t* pnode = &list->head; pnode != &list->tail; pnode = pnode->next){
//制造三个游标
node_t* pfirst = pnode;
node_t* pmid = pfirst->next;
node_t* plast = pmid->next;
if(data == pmid->data && pmid != &list->tail){
del_node(pfirst, pmid, plast);
}
}
}
//删除第一个有效结点
void list_del_head(list_t* list){
//判断链表是否为空
if(list_empty(list)){
printf("链表为空,无法删除\n");
return;
}
//head<-------->10<-------->20<---------->30<---------->tail
//pfirst pmid plast
//定义游标
node_t* pfirst = &list->head;
node_t* pmid = pfirst->next;
node_t* plast = pmid->next;
//删除pmid指向的结点
del_node(pfirst, pmid, plast);
}
//删除最后一个有效结点
//让pmid指向要删除的结点 - 指向最后一个有效结点
void list_del_tail(list_t* list){
//判断链表是否为空
if(list_empty(list)){
printf("链表为空,无法删除\n");
return;
}
//head<-------->10<-------->20<---------->30<---------->tail
// pfirst pmid plast
//定义游标
node_t* plast = &list->tail;//plast指向了tail结点
node_t* pmid = plast->prev;//plast的上一个结点 - 最后一个有效结点 - pmid
node_t* pfirst = pmid->prev;//指向pmid的上一个结点
//删除pmid指向的结点
del_node(pfirst, pmid, plast);
}
//根据结点编号来获取数据的函数
//数组:
// 10 20 30 40 数组
// 0 1 2 3 数组下标
//双链表:
//10 20 30 40 双链表数据
//0 1 2 3 结点编号
//根据结点编号来获取编号对应的数据
int list_get(list_t* list, int index){
int count = 0;//表示循环的次数
//head 10 20 30 40 tail 双链表数据
// 0 1 2 3 结点编号
//pfirst pmid plast
//tips:根据循环的次数,判断pmid指向的内容
for(node_t* pnode = &list->head; pnode != &list->tail; pnode = pnode->next){
//制造三个游标
node_t* pfirst = pnode;
node_t* pmid = pfirst->next;
node_t* plast = pmid->next;
if(index == count && pmid != &list->tail){
return pmid->data;
}
count++;
}
return -1;
}
//清除链表中的所有内容
void list_deinit(list_t* list){
//head---------10------------20----------30-----------tail
//head---------tail
//head---------20----------30-----------tail
//pfirst pmid plast
while(list->head.next != &list->tail){
node_t* pfirst = &list->head;
node_t* pmid = pfirst->next;
node_t* plast = pmid->next;
del_node(pfirst, pmid, plast);//删除pmid指向的结点
}
}
//打印
void list_printf(list_t* list){
int size = list_size(list);
printf("有%d个元素\n元素分别为:", size);
for(int i = 0; i < size; i++){
printf("%d ", list_get(list, i));
}
printf("\n");
}
main.c
//双链表测试
//main.c
#include "list.h"
int main(void){
list_t list;
list_init(&list);//初始化
//各种插入结点
list_add_head(&list, 100);
list_add_head(&list, 30);
list_add_tail(&list, 70);
list_add_tail(&list, 50);
list_add(&list, 60);
list_add(&list, 80);
list_add(&list, 20);
list_add(&list, 40);
list_add(&list, 90);
list_add(&list, 10);
list_add(&list, 110);
//获取链表内容
list_printf(&list);
//各种删除结点
list_del(&list, 90);
list_printf(&list);
list_del_head(&list);
list_printf(&list);
list_del_tail(&list);
list_printf(&list);
list_deinit(&list);//释放
return 0;
}
结果
tarena@TNV:list$ vim main.c
tarena@TNV:list$ gcc list.c main.c -o list
tarena@TNV:list$ ./list
有11个元素
元素分别为:10 20 30 40 60 80 90 100 70 50 110
有10个元素
元素分别为:10 20 30 40 60 80 100 70 50 110
有9个元素
元素分别为:20 30 40 60 80 100 70 50 110
有8个元素
元素分别为:20 30 40 60 80 100 70 50
16.递归和递推
递归和递推函数
main函数 调用 foo函数
main函数 - 调用函数
foo函数 - 被调用函数
递归函数 - 函数自身调用自身
函数A 调用 函数A
void love(int n){
if(n > 1){
love(n - 1);
}
printf("love %d times\n", n);
}
int main(void){
love(3);
return 0;
}
逐层调用, 逐层返回
什么时候去使用递归函数
正要做的是就是正在做的事, 就可以使用递归
做得事情都是完全一样的, 参数不同而已
假设递归函数已经可以使用了 ☆
无限递归 - 死递归
特别注意终止条件 - 自身调用自身停不下来
递归到什么条件下就不再适用了, 终止条件
实现n的阶乘
n! = 1 * 2 * 3 * ... * n fact(n)
(n-1)! = 1 * 2 * 3 * ... * (n-1) fact(n-1)
n! = n * (n-1)!
fact(n) = n * fact(n-1)
1! = 1
recur.c
#include <stdio.h>
int fact(int n){
if(n == 1)
return 1;
return fact(n-1)*n;
}
int main(void){
printf("5!=%d\n", fact(5));
return 0;
}
tarena@TNV:day27$ vim recur.c
tarena@TNV:day27$ gcc recur.c -o recur
tarena@TNV:day27$ ./recur
5!=120
斐波那契数列 - fibonacci
第n个数等于前两个数据的和
1 1 2 3 5 8 13 21 34 55 89 144 ..
f(n) = f(n-1) + f(n-2)
vim fibo.c
#include <stdio.h>
int fibo(int n){
if(n == 1 || n == 2)
return 1;
return fibo(n-1) + fibo(n-2);
}
int main(void){
int n = 1;
printf("请输入第几个数:");
scanf("%d", &n);
for(int i = 1; i <= n; i++){
printf("fibo(%d) = %d\n",i, fibo(i));
}
return 0;
}
tarena@TNV:day27$ vim fibo.c
tarena@TNV:day27$ gcc fibo.c -o fibo
tarena@TNV:day27$ ./fibo
请输入第几个数:10
fibo(1) = 1
fibo(2) = 1
fibo(3) = 2
fibo(4) = 3
fibo(5) = 5
fibo(6) = 8
fibo(7) = 13
fibo(8) = 21
fibo(9) = 34
fibo(10) = 55
17.二叉树
树
有序二叉树
之前研究的都是 - 一对一的结构
现实存在很多一对多的情况 - 研究一对多的数据结构 - 树
树中的每个元素称为节点(node)
每个节点都有零个或者多个子节点
没有父结点的节点称为根节点,
每一个非根节点有且仅有一个父结点
根节点左侧的部分可以看做是一颗新的树, 这棵树就是属于根节点的左子树.
同样, 根节点右侧的部分就是其右子树
一个节点的子节点(子树)的数量称为节点的度
节点的层次 - 节点的层次从根节点开始定义, 根为第一层, 根的子节点为第二层, 依次向下分
有序二叉树
每个节点最多含有两个子树的树称为二叉树
一般来说, 当左子树不为空的时候, 左子树的元素值小于根节点, 当右子树不为空的时候, 右子树的
元素值大于根节点.
一句话, 不管站在哪个节点上, 左边总是小于右边
有序二叉树的遍历
三种方式:
先序遍历 处理节点自身的数据 - 处理左子节点 - 处理右子结点 (自-左-右)
中序遍历 处理左子节点 - 处理节点本身数据 - 处理右子节点 (左-自-右)
从小到大 - 中序
后序遍历 处理左子节点 - 处理右子结点 - 处理节点本身数据 (左-右-自)
当做的树的递归
树
根节点
左子树 右子树
树 - 左子节点 - 根节点 - 右子结点
左子树 - 排序
根节点
右子树 - 排序
一对多就是树 - 不一定
有序二叉树涉及的两个结构体
描述节点属性的结构体
struct node{
int data;//数据
//两个指针
struct node* left;//保存左子结点地址
struct node* right;//保存右子结点地址
};
描述整棵树属性的结构体
struct tree{
struct node* root;//保存根节点的首地址
int cnt;//保存有效节点个数
};
mkdir tree
vim tree.h
vim tree.c
vim main.c
左子结点 - 地址就是NULL
一个节点通过指针来找到子节点
struct node{
int data;//数据
//两个指针
struct node* next;
struct node* prev;
};
struct node{
int data;//数据
//两个指针
struct node* left;//保存左子结点地址
struct node* right;//保存右子结点地址
};
typedef struct tree{
struct node* root;//根节点
int cnt;
}tree_t;
void insert(node_t** pproot, node_t* pnew)
高级指针 - 二级指针
修改一级指针内容 - 函数传递参数的过程中 - 传递的是二级指针
修改p的指向 - 修改一级指针的指向 - 参数 必定是二级指针
将新的结点插入到有序二叉树中
pnew
1.判断有序二叉树的根节点是否为空 - 为空, 直接插入即可; 非空, 比较
2.比较
新的结点的数据和根节点的数据相比
新的结点数据小于根节点的数据 插入到左子树
将左子树当做一颗新的树插入
否则 插入到右子树
将右子树当做一颗新的树插入
删除某个节点
查找节点的递归函数
1.判断有序二叉树是否为空
为空, 不再寻找; 直接结束
2.非空
2.1.和根节点匹配
2.2.查找的数据 > 根节点数据
右子树查找
2.3.查找的数据 < 根节点数据
左子树查找
二叉树
二级指针 - 很多问题
一个一个带入 - 假设二叉树为空 - 数据插入到二叉树中
删除一个节点 -
1.找节点
2.找新爹
如果要删除的结点的左子树存在 - 将左子树插入到右子树上
3.提一级
将 要删除的结点的父节点 指向 要删除的结点的右子树
int a = 100;
int* p = &a;
int** pp = &p;
**pp
tree.c
//tree.c
#include "tree.h"
//定义遍历有序二叉树的递归函数
//参数 proot 根结点
//先序遍历
static void travel(node_t* proot){
if(proot != NULL){
printf("%d ", proot->data);//打印结点自身
travel(proot->left);//遍历左子树
travel(proot->right);//遍历右子树
}
}
//中序遍历
static void travel_mid(node_t* proot){
if(proot != NULL){
travel_mid(proot->left);//遍历左子树
printf("%d ", proot->data);//打印结点自身
travel_mid(proot->right);//遍历右子树
}
}
//后序遍历
static void travel_beh(node_t* proot){
if(proot != NULL){
travel_beh(proot->left);//遍历左子树
travel_beh(proot->right);//遍历右子树
printf("%d ", proot->data);//打印结点自身
}
}
//定义遍历函数
void tree_travel(tree_t* tree){
//调用递归函数从根节点开始遍历
printf("先序遍历:");
travel(tree->root);
printf("\n");
printf("中序遍历:");
travel_mid(tree->root);
printf("\n");
printf("后序遍历:");
travel_beh(tree->root);
printf("\n");
}
//插入新的结点
//构造一个新的结点
static node_t* create_node(int data){
node_t* pnew = (node_t *)malloc(sizeof(node_t));
pnew->data = data;
pnew->left = NULL;
pnew->right = NULL;
return pnew;//返回新结点的首地址
}
//定义插入新结点的递归函数
//第二个参数给的是根结点
//第二个参数给的是新结点的地址
static void insert(node_t** pproot, node_t* pnew){
if(*pproot == NULL){
*pproot = pnew;//插入新结点
return;
}
if((*pproot)->data > pnew->data){//插入到左子树
insert(&(*pproot)->left, pnew);
return;
}else{//插入到右子树
insert(&(*pproot)->right, pnew);
return;
}
}
//插入新结点的函数
void tree_insert(tree_t* tree, int data){
//1.构建新结点
node_t* pnew = create_node(data);
//2.调用递归函数插入新结点
insert(&tree->root, pnew);
//3.更新计数
tree->cnt++;
}
//删除某个结点
//查找某个结点的递归函数
static node_t** find(node_t** pproot, int data){
//1.为空,不再查找
if(*pproot == NULL){
return pproot;//没有找到
}
//2.比较
if((*pproot)->data == data){
return pproot;//找到了
}else if((*pproot)->data > data){
return find(&(*pproot)->left, data);//左子树
}else{
return find(&(*pproot)->right, data);//右子树
}
}
//定义查找结点的函数
static node_t** find_node(tree_t* tree, int data){
return find(&tree->root, data);
}
//定义删除指定数字所在的结点函数
void tree_del(tree_t* tree, int data){
//1.找结点
node_t** ppnode = find_node(tree, data);
if(*ppnode == NULL){
printf("没找到对应的结点\n");
return;
}
//2.找亲爹
if((*ppnode)->left != NULL){
insert(&(*ppnode)->right, (*ppnode)->left);
}
//3.题一级
node_t* ptmp = *ppnode;//临时备份要删除的结点
*ppnode = (*ppnode)->right;//要删除结点的右子结点
free(ptmp);//释放要删除的结点内存
tree->cnt--;//更新计数
}
//定义清空结点的递归函数
static void clear(node_t** pproot){
if(*pproot != NULL){
clear(&(*pproot)->left);//清空左子树
clear(&(*pproot)->right);//清空右子树
free(*pproot);//释放结点的内存
*pproot = NULL;//让结点的父结点的l/r指向NULL
return;
}
}
//释放二叉树所有结点的函数
void tree_clear(tree_t* tree){
//调用递归函数清空结点
clear(&tree->root);
tree->cnt = 0;//结点个数清0
}
//定义修改结点值的函数
void tree_modify(tree_t* tree, int old_data, int new_data){
//1.先删除
tree_del(tree, old_data);
//2.插入新结点
tree_insert(tree, new_data);
}
tree.h
//tree.h
#ifndef _TREE_H
#define _TREE_H
#include <stdio.h>
#include <stdlib.h>
//描述结点属性的结构体
typedef struct node{
int data;//数据
struct node* left;//保存左子树结点地址
struct node* right;//保存右子树结点地址
}node_t;
//描述有序二叉树的结构体
typedef struct tree{
struct node* root;//根结点
int cnt;//结点的个数
}tree_t;
//函数声明
extern void tree_travel(tree_t* tree);//遍历
extern void tree_insert(tree_t* tree, int data);//插入
extern void tree_clear(tree_t* tree);//清空所有结点
extern void tree_del(tree_t* tree, int data);//删除指定数字所在结点
extern void tree_modify(tree_t* tree, int old_data, int new_data);//修改
#endif
main.c
//有序二叉树测试
//main.c
#include "tree.h"
int main(void){
//种树
tree_t tree;
//初始化
tree.root = NULL;
tree.cnt = 0;
//添加新结点
tree_insert(&tree, 50);
tree_insert(&tree, 70);
tree_insert(&tree, 60);
tree_insert(&tree, 20);
tree_insert(&tree, 40);
tree_insert(&tree, 30);
tree_insert(&tree, 10);
tree_insert(&tree, 90);
tree_insert(&tree, 80);
tree_insert(&tree, 100);
tree_insert(&tree, 52);
//遍历
tree_travel(&tree);
//删除结点
tree_del(&tree, 40);
tree_del(&tree, 60);
printf("---------------------------------------------\n");
//遍历
tree_travel(&tree);
//修改
tree_modify(&tree, 50, 520);
tree_modify(&tree, 70, 1314);
printf("---------------------------------------------\n");
//遍历
tree_travel(&tree);
//释放
tree_clear(&tree);
return 0;
}
运行结果
tarena@TNV:tree$ vim main.c
tarena@TNV:tree$ gcc tree.c main.c -o tree
tarena@TNV:tree$ ./tree
先序遍历:50 20 10 40 30 70 60 52 90 80 100
中序遍历:10 20 30 40 50 52 60 70 80 90 100
后序遍历:10 30 40 20 52 60 80 100 90 70 50
---------------------------------------------
先序遍历:50 20 10 30 70 52 90 80 100
中序遍历:10 20 30 50 52 70 80 90 100
后序遍历:10 30 20 52 80 100 90 70 50
---------------------------------------------
先序遍历:90 80 52 20 10 30 100 520 1314
中序遍历:10 20 30 52 80 90 100 520 1314
后序遍历:10 30 20 52 80 1314 520 100 90
18.基本排序和查找算法
基本排序和查找算法
4个排序算法 + 2个查找算法
1.冒泡排序算法
举例:
初始状态: 9 7 5 3 1
第一趟 : 7 5 3 1 9 (4次)
第二趟 : 5 3 1 7 9 (4次)
第三趟 : 3 1 5 7 9 (4次)
第四趟 : 1 3 5 7 9 (4次)
目前来看 : n个数据需要比较n-1趟, 每趟需要比较n-1次
优化
初始状态: 9 7 5 3 1
第一趟 : 7 5 3 1 9 (4次)
第二趟 : 5 3 1 7 9 (3次)
第三趟 : 3 1 5 7 9 (2次)
第四趟 : 1 3 5 7 9 (1次)
目前来看 : n个数据需要比较n-1趟, 每趟需要比较n-i次
初始状态: 0 1 7 5 3
第一趟 : 0 1 5 3 7 (4次)
第二趟 : 0 1 3 5 7 (3次)
第三趟 : 0 1 3 5 7 (2次)
------------------------------第四趟可以省略
第四趟 : 0 1 3 5 7 (1次)
目前来看 : 如果发现某趟排序[没有发生数据交换]说明排序完成了
mkdir sort
vim sort.h
vim sort.c
vim main.c
冒泡排序
sort.h
//sort.h
#ifndef _SORT_H
#define _SORT_H
#include <stdio.h>
#include <stdlib.h>
extern void bubble_sort(int data[], int size);
extern void bubble_print(int data[], int size);
#endif
sort.c
//sort.c
#include "sort.h"
//定义冒泡算法函数
void bubble_sort(int data[], int size){
bubble_print(data, size);
int i,j;
for(i = 0; i < size - 1; i++){//趟次
//判断是否发生了交换,没有,flag=1;交换,flag=0
int flag = 1;
for(j = 0; j < size-1-i; j++){//没趟比较的次数
if(data[j] > data[j+1]){//交换
int swap = data[j];
data[j] = data[j+1];
data[j+1] = swap;
flag = 0;//标记数据发生了交换,需要继续比较
}
}
if(flag)
break;//如果没有发生数据交换,排序结束
}
bubble_print(data, size);
}
void bubble_print(int data[], int size){
printf("data[%d] = ", size);
for(int i = 0; i < size; i++){
printf("%d ", data[i]);
}
printf("\n");
}
main.c
//main.c :排序算法测试
#include "sort.h"
int main(void){
int data[] = {9, 0, 7, 2, 5, 4, 1, 6, 3, 8};
int size = sizeof(data) / sizeof(data[0]);
bubble_sort(data, size);
return 0;
}
运行结果
tarena@TNV:sort$ vim sort.h
tarena@TNV:sort$ vim sort.c
tarena@TNV:sort$ gcc main.c sort.c -o main
tarena@TNV:sort$ ./main
data[10] = 9 0 7 2 5 4 1 6 3 8
data[10] = 0 1 2 3 4 5 6 7 8 9
插入排序
2.插入排序
定义一个变量暂存备份被插入的数据, 然后拿着被插入的数据 在前面有序的数列中从后向前扫描, 找到对应的位置插入即可
初始状态:
10 30 20 15 60 //无序数列
10 30 20 15 60
-- -- -- -- --
0 1 2 3 4 数组下标
1.下标为0的数据先不要移动, 让其在原先的位置上
2.要插入的数据是30
int inserted = 30;
30从后向前比较, 和10比较, 10<30, 10(0) 30(1)
10 30
-- -- -- -- --
0 1 2 3 4 数组下标
3.要插入的数据是20
int inserted = 20;
20从后向前比较, 和30比较, 20<30, 20(1) 30(2)
和10比较, 20>10, 10(0) 20(1) 30(2)
10 20 30
-- -- -- -- --
0 1 2 3 4 数组下标
4.要插入的数据是15
int inserted = 15;
15从后向前比较, 和30比较, 15<30, 15(2) 30(3)
和20比较, 15<20, 15(1) 20(2) 30(3)
和10比较, 15>10, 10(0) 15(1) 20(2) 30(3)
10 15 20 30
-- -- -- -- --
0 1 2 3 4 数组下标
5.要插入的数据是60
int inserted = 60;
60从后向前比较, 和30比较, 60>30, 30(3) 60(4)
10 15 20 30 60
-- -- -- -- --
0 1 2 3 4 数组下标
被插入的数据 - 从第二个数据开始插入
sort.c
//sort.c
#include "sort.h"
//插入排序函数
//10 30 20 15 60
//0 1 2 3 4
void insert_sort(int data[], int size){
insert_print(data, size);
int i;
for(i = 1; i < size; i++){
int inserted = data[i];//暂存要插入的数据
int j;
//从后往前找到对应的插入位置
// j=1 i=1; j>0 && 30<data[0] -> 假 ->结束循环 j==1
// j=1 i=1; j>0 && 30<data[0] -> 真 ->data[1] = data[0]
// j=0 i=1; j>0 ->假
for(j = i; j > 0 && inserted < data[j-1]; --j){
data[j] = data[j-1];
}
//如果要插入的数据比前面有序的数列都大,位置不动,无须插入一遍
if(j != i)
data[j] = inserted;//插入数据
}
insert_print(data, size);
}
void insert_print(int data[], int size){
printf("data[%d] = ", size);
for(int i = 0; i < size; i++){
printf("%d ", data[i]);
}
printf("\n");
}
sort.h
//sort.h
#ifndef _SORT_H
#define _SORT_H
#include <stdio.h>
#include <stdlib.h>
extern void insert_sort(int data[], int size);
extern void insert_print(int data[], int size);
#endif
main.c
//main.c :排序算法测试
#include "sort.h"
int main(void){
int data[] = {9, 0, 7, 2, 5, 4, 1, 6, 3, 8};
int size = sizeof(data) / sizeof(data[0]);
insert_sort(data, size);
return 0;
}
运行结果
tarena@TNV:insert$ vim main.c
tarena@TNV:insert$ gcc sort.c main.c -o sort
tarena@TNV:insert$ ./sort
data[10] = 9 0 7 2 5 4 1 6 3 8
data[10] = 0 1 2 3 4 5 6 7 8 9
选择排序
选择排序
对于n个无序的数列遍历n-1次, 每次选中剩余的数据的最小值, 放在对应的位置上
int data[5] = {9, 7, 3, 5, 1};
9 7 3 5 1
_ _ _ _ _
0 1 2 3 4
第一次遍历:从下标为1的数据, 找出数据最小的值的下标, 然后和下标为0的数据交换位置
1 7 3 5 9
_ _ _ _ _
0 1 2 3 4
最小的值为1, 下标是4
int tmp = data[0];
data[0] = data[4];
data[4] = tmp;
找到所有数据的最小值, 和下标为0的值做替换
第二次遍历 : 找出第二小的值, 和下标为1的位置做替换
1 3 7 5 9
_ _ _ _ _
0 1 2 3 4
最小的值为3, 下标为2
int tmp = data[1];
data[1] = data[2];
data[2] = tmp;
第三次遍历 : 找出剩余最小的值, 和下标为2的位置做替换
1 3 5 7 9
_ _ _ _ _
0 1 2 3 4
最下的值为5, 下标为3
int tmp = data[2];
data[2] = data[3];
data[3] = tmp;
第四次遍历 : 找出剩余最小的值, 和下标为3的位置做替换
1 3 5 7 9
_ _ _ _ _
0 1 2 3 4
最小的值为7, 下标为3 不再需要替换
1 5 3 2 7
1 5 3 2 7
1 2 3 5 7
1 2 3 5 7
sort.h
//sort.h
#ifndef _SORT_H
#define _SORT_H
#include <stdio.h>
#include <stdlib.h>
extern void select_sort(int data[], int size);
extern void print(int data[], int size);
#endif
sort.c
//sort.c
#include "sort.h"
void select_sort(int data[], int size){
print(data, size);
int i;
for(i = 0; i < size-1; i++){
int min = i;//假设无序数列中第一个数据最小,min记录数据最小的数
int j;
for(j = i+1; j < size; j++){//最小数
if(data[min] > data[j]){
min = j;
}
}
if(min != i){//数据交换
int swap = data[i];
data[i] = data[min];
data[min] = swap;
}
}
print(data, size);
}
void print(int data[], int size){
printf("a[%d] : ", size);
for(int i = 0; i < size; i++){
printf("%d ", data[i]);
}
printf("\n");
}
main.c
//main.c
#include "sort.h"
int main(void){
int data[] = {9, 0, 8 , 2, 5, 3, 4, 1, 7, 6};
int size = sizeof(data) / sizeof(data[0]);
select_sort(data, size);//选择排序
return 0;
}
运行结果
tarena@TNV:choose$ vim sort.c
tarena@TNV:choose$ gcc main.c sort.c -o sort
tarena@TNV:choose$ ./sort
a[10] : 9 0 8 2 5 3 4 1 7 6
a[10] : 0 1 2 3 4 5 6 7 8 9
快速排序
快速排序 - 递归函数
例如:
0 10 80 30 60 50 40 70 20 90 100
思路 :
找出一个基准值, 其左边的数据小于他, 他右边的数据大于他
第一次实现 : 让数据50作为基准值, 进行依次分组,
把比50小的数据放在其左边, 把比50大的数据放在其右边
第二次实现:
对50左边的数据分组 对50右边的数据分组
对50左边的数据分组 -> 以30为基准
对50右边的数据分组 -> 以90为基准
第三次实现:
对30的左边分组 -> 10
对90的左边分组 -> 70
直到剩余最后一个或者没有 - 无需分组
以50为基准 : 0 10 30 40 20 <50> 80 60 70 90 100
30,90为基准 : 0 10 20 <<30>> 40 <50> 80 60 70 <<90>> 100
10,70为基准 : 0 <10> 20 <<30>> 40 <50> 60 <70> 80 <<90>> 100
0, 10, 20 - 看起来不需要 - 实际上需要
xx, xx, xx - 三个数据, 比30小
通过三个游标来实现:
定义三个游标来实现原地分组
pivot = 基准值
int i = 0;//左边界
int j = size - 1;//右边界
int pivot = data[p];//将基准值备份到pivot变量中, 基准值的下标为p
pivot = 50;//存储了基准值
0 10 80 30 60 50 40 70 20 90 100
i
p
j
for(; !(i>=p || pivot < data[i]); i++);
i >= p -> 假
pivot < data[i] -> 假
当i<p, data[i]< pivot, i++
何时不i++, i>p || data[i] > pivot
data[i] > pivot -> 该条件成立
data[p] = data[i];
p = i;
0 10 80 30 60 80 40 70 20 90 100
i
p
j
当j>p, data[j] > pivot, j--
何时不j--, j<p || data[j] < pivot
data[j] < pivot -> 该条件成立
data[p] = data[j];
p = j;
0 10 20 30 60 80 40 70 20 90 100
i
p
j
当i<p, data[i]< pivot, i++
何时不i++, i>p || data[i] > pivot
data[p] = data[i];
p = i;
0 10 20 30 60 80 40 70 60 90 100
i
p
j
当j>p, data[j] > pivot, j--
何时不j--, j<p || data[j] < pivot
data[j] < pivot -> 该条件成立
data[p] = data[j];
p = j;
0 10 20 30 40 80 40 70 60 90 100
i
p
j
i变化 - j变化 - i变化 ....
0 10 20 30 40 80 80 70 60 90 100
i
p
j
停止, 直到i=p=j, 此时就是基准值应该在的位置
data[p] = pivot;
0 10 20 30 40 50 80 70 60 90 100
i
p
j
i变化, j变化, i变化, j变化, ...
0 10 20 30 40 50 80 70 60 90 100
0 1 2 3 4 5 6 7 8 9 10
l p r
left right
p - left > 1 -> 基准值p左边的数据大于1个 排序
right - p > 1 -> 基准值p右边的数据大于1个 排序
quick.h
//quick.h
#ifndef _QUICK_H
#define _QUICK_H
#include <stdio.h>
extern void quick_sort(int data[], int left, int right);
extern void print(int data[], int size);
#endif
quick.c
//quick.c
#include "quick.h"
//0 10 80 30 60 50 40 70 20 90 100
//l
// p
// j
void quick_sort(int data[], int left, int right){
int p = (left + right) / 2;//定义基准值下标,随意
int i = left;//left=0,左边界
int j = right;//right=size-1,右边界
int pivot = data[p];//将基准值保存到pivot中
while(i < j){//如果 i == j,停止分组
//i变化
//i++
//for(;!(i >= p || data[i] > pivot); i++);
for(;i < p && data[i] < pivot; i++);
//data[i] > pivot
if(i < p){
data[p] = data[i];
p = i;
}
//j变化
//j--
//for(;!(j <= p || data[j] < pivot); j--);
for(;j > p && data[j] > pivot; j--);
//data[j] < pivot
if(j > p){
data[p] = data[j];
p = j;
}
}
data[p] = pivot;//分组结束将基准值放到p的位置上
if(p - left > 1)//基准左侧的数据大于1个,继续分组
quick_sort(data, left, p-1);//左边界为0,右边界为p-1
if(right - p > 1)//基准值右侧的数据大于1个,继续分组
quick_sort(data, p+1, right);//左边界不包含p,右边界为right
}
void print(int data[], int size){
printf("a[%d] : ", size);
for(int i = 0; i < size; i++){
printf("%d ", data[i]);
}
printf("\n");
}
main.c
//main.c
#include "quick.h"
int main(void){
int data[] = {0, 10, 80, 30, 60, 50, 40, 70, 20, 90, 100};
int size = sizeof(data) / sizeof(data[0]);
print(data, size);
quick_sort(data, 0, size-1);
print(data, size);
return 0;
}
运行结果
tarena@TNV:quicksort$ vim quick.c
tarena@TNV:quicksort$
tarena@TNV:quicksort$ gcc main.c quick.c -o quick
tarena@TNV:quicksort$ ./quick
a[11] : 0 10 80 30 60 50 40 70 20 90 100
a[11] : 0 10 20 30 40 50 60 70 80 90 100
线性查找
线性查找
在数列中挨个匹配要查找的数据, 对于数据的有序性没有要求
mkdir find
vim find.h
vim find.c
vim sort.h
vim sort.c
vim main.c
折半查找
折半查找 - 二分查找 - 递归函数
前提 - 数据必须有序, 无序则先排序
让数列对半分, 拿着目标值和中间值进行比较
相等 - 返回下标
目标值 > 中间值 -> 右侧继续二分查找
目标值 < 中间值 -> 左侧继续二分查找
10 20 30 40 50 60 70
0 1 2 3 4 5 6
l m-1 mid m+1 r
10 20 30 40 50
0 1 2 3 4
l m-1 mid m+1/r
int recu_find(int data[], int left, int right, int key){
}
60 -> 中间值
find.h
//find.h
#ifndef _FIND_H
#define _FIND_H
#include <stdio.h>
extern int line_find(int data[], int size, int key);//线性查找
extern int half_find(int data[], int size, int key);//折半查找
#endif
find.c
//find.c
#include "find.h"
//线性查找
int line_find(int data[], int size, int key){
for(int i = 0; i < size; i++){
if(data[i] == key)
return i;//找到并返回对应的下标
}
return -1;//没有找到
}
//定义递归二分查找函数
static int recu_find(int data[], int left, int right, int key){
if(left <= right){
int mid = (left + right) / 2;
if(data[mid] > key){
return recu_find(data, left, mid-1, key);//左侧二分查找
}else if(data[mid] < key){
return recu_find(data, mid+1, right, key);//右侧二分查找
}else{
return mid;//找到了
}
}
return -1;//没有找到
}
//定义二分查找
int half_find(int data[], int size, int key){
return recu_find(data, 0, size-1, key);
}
sort.h
//sort.h
#ifndef _SORT_H
#define _SORT_H
#include <stdio.h>
extern void quick_sort(int data[], int left, int right);
extern void print(int data[], int size);
#endif
sort.c
//sort.c
#include "sort.h"
void quick_sort(int data[], int left, int right){
int p = (left + right) / 2;//定义基准下标
int i = left;
int j = right;
int pivot = data[p];
while(i < j){
while(i < p && data[i] < pivot)
i++;
if(i < p){
data[p] = data[i];
p = i;
}
while(j > p && data[j] > pivot)
j--;
if( p < j){
data[p] = data[j];
p = j;
}
}
data[p] = pivot;
if(p - left > 1){
quick_sort(data, left, p-1);
}
if(right - p >1){
quick_sort(data, p+1, right);
}
}
void print(int data[], int size){
printf("a[%d] : ", size);
for(int i = 0; i < size; i++){
printf("%d ", data[i]);
}
printf("\n");
}
main.c
//main.c
#include "find.h"
#include "sort.h"
int main(void){
int data[] = {9, 0, 8, 2, 5, 3, 4, 1, 7, 6};
int size = sizeof(data) / sizeof(data[0]);
int key = 1;
int index = -1;
//顺序查找
index = line_find(data, size, key);
if(index == -1){
printf("没有找到\n");
return -1;
}
printf("找到了:data[%d] = %d\n", index, data[index]);
print(data, size);
//排序
quick_sort(data, 0, size-1);
print(data, size);
//二分查找
index = half_find(data, size, key);
if(index == -1){
printf("没有找到\n");
return -1;
}
printf("找到了:data[%d] = %d\n", index, data[index]);
return 0;
}
运行结果
tarena@TNV:find$ vim main.c
tarena@TNV:find$ vim find.c
tarena@TNV:find$ gcc main.c find.c sort.c -o find
tarena@TNV:find$ ./find
找到了:data[7] = 1
a[10] : 9 0 8 2 5 3 4 1 7 6
a[10] : 0 1 2 3 4 5 6 7 8 9
找到了:data[1] = 1
案例:
案例:
错误-error总结
1.stray - 程序中有中文字符
2.浮点数例外(核心已转储)
内存错误 - 程序崩溃
3.undeclared - 未定义 -
单词写错了
变量未定义
......
4.first use int this function - 在当前函数第一次用
5.previous - 前一个
6.redefinition - 重定义
思路:
1.scanf - 双引号中只能出现占位符