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;  ------------------------------>1while(i <= n){---------------------------->10001次
            i++;---------------------------------->10000printf("go step : %d\n", i)----------->10000}
        printf("go step more than %d\n", i)------->1T(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 + 1 
    T(1) = 1 + 1 + 1 = 3
    T(10000) = 100000000 + 1000 + 1
             = 100010001

    O()

    很多代码 
        顺序执行 - 可以忽略  
        循环执行 
        多层循环 - 最内层循环 
===============================
研究什么问题 - 
    逻辑结构 - 数据和数据之间的关系 
        集合结构 
            集合中的数据,同属于一个集合,没有任何其他关系
        线性结构
            线性结构中的数据元素之间是一对一的先后关系 
        树形结构
            一对多的关系 
        图形结构 
            多对多的关系 

    存储结构/物理结构 - 数据在计算机中的存储形式 
        顺序存储结构 
            数据元素放在连续的存储单元里
            逻辑上相连的元素在物理上也是相连的
        链式存储结构
            逻辑上相邻的数据元素在物理上可以不相邻 
            借助于元素存储的指针来表示元素之间的逻辑关系 
            吃饭排队拿号 - 银行办业务 

        顺序 - 链式 
            顺 - 物理上是连续的 
            链 - 非顺序, 可以是离散的
                影响存储空间分配的方便程度 - 链式 
                影响数据运算的速度 - 顺序 

        索引存储结构 
            在存储元素信息的同时, 建立附加的索引表 
        散列存储结构   - 哈希存储 
            根据元素的关键字直接计算出该元素的存储地址 
    数据运算 
        运算的定义 - 针对逻辑结构
        运算的实现 - 针对物理结构 

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的左边分组 -> 1090的左边分组 -> 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 - 双引号中只能出现占位符

endl

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

良辰美景好时光

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值