四十七、 数据结构
47.1 什么是数据结构
数据结构是计算机存储、组织数据的方式
是指相互之间存在一种或多种特定关系的数据元素的集合
- 数据: 整数、文件、视频、图片,能够被计算机存储和识别的
- 结构: 数据与数据间的关系
- 算法: 解决问题的求解方法
数据结构和编程语言无关
使用数据结构为了能够更好对数据进行处理
47.2 数据结构的三要素
- 存储结构
- 逻辑结构
- 算法
存储结构:
存储结构 :
顺序存储 可以类比数组,计算机中一般都是顺序存储
链式存储 想想自行车链条,或者谁谁谁的大金链子,这一环扣着下一环
散列存储/索引存储 哈希表,不连续的,建议搜百度
逻辑结构:
逻辑结构:
一对一 : 线性的,比如大金链子,这一环扣下一环,不乱扣
除第一个数据结点外,每一个数据结点都有一个唯一前驱结点
除最后一个数据结点外,每一个数据结点都有一个直接后继结点
一对多 : 树形,一个根有好几个树杈,一个树杈又有好几个子树杈
第一个元素结点无前驱结点,最后一个元素结点无后继结点
其他的元素结点,只有唯一前驱结点,没有唯一后继结点
多对多 : 图,想想地图上的车站
这个车站可以直接去好几个其他车站
好几个其他车站也能直接到这个车站
一个结点可以有很多后继结点,也可以有很多个前驱结点
算法:
进行运算或操作的方法
算法(Algorithm)是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令
算法代表着用系统的方法描述解决问题的策略机制。
能够对一定规范的输入,在有限时间内获得所要求的输出。
算法五要素:
- 有穷性: 算法必须能在执行有限个步骤之后终止
- 确定性: 算法的每一步骤必须有确定的意义
- 可行性: 算法中执行的任何计算步骤都可以被分解为基本的可执行的操作步骤,每个计算步骤都可以在有限时间内完成(也称之为有效性)
- 输入: 一个算法有0个或多个输入,以刻画运算对象的初始情况,所谓0个输入是指算法本身定出了初始条件
- 输出: 一个算法有一个或多个输出,以反映对输入数据加工后的结果,没有结果的算法是毫无意义的
47.3 数据,数据元素,数据项,数据对象
- 数据: 客观事物的符号表示,是所有能输入计算机中并被计算机程序处理的符号的总称
- 数据元素: 数据的基本单位,在计算机中通常作为一个整体进行考虑和处理。(有“原子型”,不可分割的;“由多个款项构成”,每个款项成为一个数据项)
- 数据项: 组成数据元素的、有独立含义的、不可分割的最小单位
- 数据对象: 性质相同的数据元素的集合,是数据的一个子集
不清楚的可以看下这个链接的讲解:
https://codebuilding.blog.csdn.net/article/details/79868242?spm=1001.2101.3001.6650.5&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-5-79868242-blog-117795840.235%5Ev38%5Epc_relevant_sort_base2&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-5-79868242-blog-117795840.235%5Ev38%5Epc_relevant_sort_base2&utm_relevant_index=4
四十八、线性表浅谈(特别浅的谈)
48.1 线性表概念
- 多个同一特征的数据元素组成的线性结构,就叫做线性表
48.2 线性表分类
线性表根据具体实现的不同,可以分为:
- 顺序表
- 链表
- 栈 (操作受限的线性表)
- 队列 (操作受限的线性表)
48.3 线性表特点
- 同一特征的多个数据类型
- 数据之间是一对一的关系
四十九、顺序表
49.1 顺序表概念
- 顺序存储的数据之间是
一对一
关系的,多个同一特征元素的结构 - 逻辑结构上连续的元素之间
物理存储结构
上也连续
49.2 顺序表实现思路
- 可以直接通过数组实现,可以直接分配在栈区,也可以手动分配在堆区
- 由于涉及到对顺序表元素的,增删改查,所以需要记录顺序表的实际长度
49.3 顺序表的实现
49.3.1 顺序表的结构体
- 需要一个数组
- 以及一个实际长度
typedef int datatype;
typedef struct SeqList
{
datatype arr[MAX];
int len;
}Seq,*Seqptr;
Seq : 结构体类型
Seqptr : 结构体指针类型
49.3.2 需要实现的顺序表的功能
顺序表的结构体声明:
// 顺序表最多十个结点
#define MAX 10
// 给int类型起个别名为data
typedef int data;
// 顺序表结构体声明,并起别名为list
typedef struct mylist
{
data arr[MAX]; // 存放数据的数组
int index; // 实际存放的个数
}list;
- 增删改查(CURD)
// 创建顺序表
list *init();
// 顺序表的判满
int is_full(list *L);
// 顺序表的判空
int is_empty(list *L);
// 顺序表的尾插
int tail_insert(list *L, data number);
// 顺序表的头插
int head_insert(list *L, data number);
// 指定下标插入
int inset_by_index(list *l, int index, data value);
// 指定下标删除
int del_by_index(list *l, int index);
// 顺序表的按值查找,返回下标
int find_by_value(list *l, data value);
// 顺序表的按值修改,返回下标
int change_by_value(list *l, data old_value, data new_value);
// 顺序表的按位置查找,返回对应的值
data find_by_index(list *l, int i);
// 顺序表的选择排序
int select_sort(list *l);
// 顺序表的冒泡排序
int bubble_sort(list *l);
// 输出顺序表
int output(list *l);
// 头删
int head_del(list *l);
// 尾删
int tail_del(list *l);
// 顺序表去重
int remove_duplicates(list *l);
// 释放顺序表
int free_list(list **l);
49.4 顺序表功能函数的实现分析
- 创建顺序表
// 创建顺序表
list *init();
// 在堆区申请list这么大的空间
list *p = (list *)malloc(sizeof(list));
// 将顺序表的内容清零
memset(p->arr, 0, sizeof(p->arr));
// 实际存放数清零
p->index = 0;
- 顺序表的判满
// 顺序表的判满
int is_full(list *L);
// 实际存放数量小于最大存放数量时未满
if (L->index < MAX)
return 0; // 未满返回0
else
return 1; // 满返回1
- 顺序表的判空
// 顺序表的判空
int is_empty(list *L);
// 实际存放数量为0时为空
if (0 == L->index)
return 1; // 空则返回1
else
return 0; // 不空则返回0
- 顺序表的尾插
// 顺序表的尾插
int tail_insert(list *L, data number);
// 以实际存储数量为下标进行插入
// 实际存储数量会比里面存放的最后一个数据的下标多1
L->arr[L->index] = number;
// 插入后实际存放数量+1
L->index++;
- 顺序表的头插
// 顺序表的头插
int head_insert(list *L, data number);
// 实际存放的最后一个数据到第一个数据都向后移动一位
for (int i = L->index; i > 0; i--)
{
// 逐位覆盖后移,将 头部位置 空出
L->arr[i] = L->arr[i - 1];
// 其实第一位还有之前的数据,只是在第二位复制了一份
// 所以第一位的数据可以进行修改了
}
// 在头部插入,即下标为0的位置
L->arr[0] = number;
// 插入后实际存放数量+1
L->index++;
- 指定下标插入
// 指定下标插入
int inset_by_index(list *l, int index, data value);
// 从实际存储的最后一个位置的下一位开始
// 到指定的下标位置,逐个往后移动一位
for (int i = l->index; i > index - 1; i--)
{
// 前一位覆盖后一位
l->arr[i] = l->arr[i - 1];
}
// 将数据存入指定下标位置
l->arr[index] = value;
// 实际存放数据数+1
l->index++;
- 指定下标删除
// 指定下标删除
int del_by_index(list *l, int index);
// 从指定下标开始,到实际存入数据最后位置的前一位
// 后一位覆盖当前位置
// 若删除最后一位的元素,则不会进入此循环,会直接将 L.index 减1
for (int i = index; i < l->index - 1; i++)
{
// 后一位覆盖当前位
l->arr[i] = l->arr[i + 1];
}
// 实际存放数据数-1
l->index--;
- 顺序表的按值查找,返回下
// 顺序表的按值查找,返回下标
int find_by_value(list *l, data value);
// 从第一个循环向最后一个找
// i < l->index 可以取到最后一个
for (int i = 0; i < l->index; i++)
{
// 如果当前下标上的值与要找的值相同,则返回下标
if (value == l->arr[i])
{
return i; // 返回下标
}
}
// 没找到就提示没找到,并返回-1,代表函数异常退出
printf("没找到这个值\n");
return -1;
- 顺序表的按值修改,返回下标
// 顺序表的按值修改,返回下标
int change_by_value(list *l, data old_value, data new_value);
// 找到这个值对应的下标
int i = find_by_value(l, old_value);
// 将这个下标上的值修改为新的值
l->arr[i] = new_value;
// 返回下标
return i;
- 顺序表的按位置查找,返回对应的值
// 顺序表的按位置查找,返回对应的值
data find_by_index(list *l, int i);
// 返回对应下标的值,数组这点很方便
return l->arr[i];
- 顺序表的简单选择排序
// 顺序表的简单选择排序
int select_sort(list *l);
// 从第一位开始,往后逐个找最值,找到最值后进行交换
for (int i = 0; i < l->index - 1; i++)
{
// 假设当前下标是最大值所在的下标
int max = i;
// 从当前的下一个位置开始比较
for (int j = i + 1; j < l->index; j++)
{
// 如果后面某个值比记录的最大值下标对应的值还大,那就更新最大值下标
if (l->arr[max] < l->arr[j])
{
max = j; // 更新最大值下标
}
}
// 将最大值与当前值进行交换
// 也可以先判断记录的最大值所在下标是不是更新过,更新过再换
data temp = l->arr[max];
l->arr[max] = l->arr[i];
l->arr[i] = temp;
}
- 顺序表的冒泡排序
// 顺序表的冒泡排序
int bubble_sort(list *l);
// 循环实际存入数量-1次
for (int i = 0; i < l->index; i++)
{
// 每相邻的两位都要进行比较
for (int j = 0; j < l->index - i - 1; j++)
{
// 如果当前位置数据大于后一位,那么就交换
if (l->arr[j] > l->arr[j + 1])
{
data temp = l->arr[j];
l->arr[j] = l->arr[j + 1];
l->arr[j + 1] = temp;
}
}
}
- 输出顺序表
// 输出顺序表
int output(list *l);
// 从下标0开始,循环到实际存入的最后一个数据
for (int i = 0; i < l->index; i++)
{
// 输出数据
printf("L->arr[%d]=%d\n", i, l->arr[i]);
}
- 头删
// 头删
int head_del(list *l);
// 从第一个数据开始,后一个将当前覆盖
// 至倒数第二个实际存入的数据
// 因为倒数第二个也是后一位覆盖当前
for (int i = 0; i < l->index - 1; i++)
{
// 后一个覆盖当前
l->arr[i] = l->arr[i + 1];
}
// 实际存入数据数-1
l->index--;
- 尾删
// 尾删
int tail_del(list *l);
// 实际存入数-1就够了,这样就无法正常访问到原来最后的数据了
// 不放心可以按照下标将最后的数据清空
l->index--;
- 顺序表去重
// 顺序表去重
int remove_duplicates(list *l);
// 从第一个数据开始为当前位置,到实际存入的倒数第二个数据
for (int i = 0; i < l->index - 1; i++)
{
// 从当前位置的下一位开始,至最后一位数据,逐位比较
for (int j = i + 1; j < l->index; j++)
{
// 如果有重复的,那么以这个地方为下标
if (l->arr[i] == l->arr[j])
{
// 根据这个地方的下标删除这个地方的数据
del_by_index(l, j);
// 被比较的往前挪一下,因为一会还要+1,-1+1相当于没动
j--;
}
}
}
- 释放顺序表
// 释放顺序表
int free_list(list **l);
// 根据传入的顺序表的地址
// 将其指向的地址(*l)的空间释放
free(*l);
// 根据传入的顺序表的地址
// 让其指向(*l)NULL
*l = NULL;
49.5 顺序表功能函数、接口及测试代码
直接上代码:
接口 : head.h
#ifndef __HEAD_H__
#define __HEAD_H__
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 顺序表最多放十个数据
#define MAX 10
// int 类型重定义为 data
typedef int data;
// 结构体声明并重定义为 list
typedef struct mylist
{
data arr[MAX];
int index;
}list;
// 创建顺序表
list *init();
// 顺序表的判满
int is_full(list *L);
// 顺序表的判空
int is_empty(list *L);
// 顺序表的尾插
int tail_insert(list *L, data number);
// 顺序表的头插
int head_insert(list *L, data number);
// 指定下标插入
int inset_by_index(list *l, int index, data value);
// 指定下标删除
int del_by_index(list *l, int index);
// 顺序表的按值查找,返回下标
int find_by_value(list *l, data value);
// 顺序表的按值修改,返回下标
int change_by_value(list *l, data old_value, data new_value);
// 顺序表的按位置查找,返回对应的值
data find_by_index(list *l, int i);
// 顺序表的选择排序
int select_sort(list *l);
// 顺序表的冒泡排序
int bubble_sort(list *l);
// 输出顺序表
int output(list *l);
// 头删
int head_del(list *l);
// 尾删
int tail_del(list *l);
// 顺序表去重
int remove_duplicates(list *l);
int free_list(list **l);
#endif
功能实现 : list.c
#include "head.h"
// 创建顺序表
list *init()
{
list *p = (list *)malloc(sizeof(list));
if (NULL == p)
{
printf("创建顺序表失败\n");
return NULL;
}
// 将顺序表的内容清零
memset(p->arr, 0, sizeof(p->arr));
// 下标清零
p->index = 0;
return p;
}
// 顺序表的判满 满则返回1
int is_full(list *L)
{
if (NULL == L)
{
printf("传入参数为空\n");
return -1;
}
if (L->index < MAX)
return 0;
else
return 1;
}
// 顺序表的判空 空则返回1
int is_empty(list *L)
{
if (NULL == L)
{
printf("传入参数为空\n");
return -1;
}
if (0 == L->index)
return 1;
else
return 0;
}
// 顺序表的尾插
int tail_insert(list *L, data number)
{
if (NULL == L)
{
printf("传入参数为空\n");
return -1;
}
else if (is_full(L))
{
printf("这个顺序表满了,无法继续插入\n");
return -1;
}
// 进行插入
L->arr[L->index] = number;
L->index++;
return 0;
}
// 顺序表的头插
int head_insert(list *L, data number)
{
if (NULL == L)
{
printf("传入参数为空\n");
return -1;
}
else if (is_full(L))
{
printf("这个顺序表满了,无法继续插入\n");
return -1;
}
// 后移 将 头部 空出
for (int i = L->index; i > 0; i--)
{
L->arr[i] = L->arr[i - 1];
}
// 在头部插入
L->arr[0] = number;
L->index++;
return 0;
}
// 顺序表的按值查找,返回下标
int find_by_value(list *l, data value)
{
if (NULL == l)
{
printf("传入参数为空\n");
return -1;
}
for (int i = 0; i < l->index; i++)
{
if (value == l->arr[i])
{
return i;
}
}
printf("没找到这个值\n");
return -1;
}
// 顺序表的按值修改,返回下标
int change_by_value(list *l, data old_value, data new_value)
{
if (NULL == l)
{
printf("传入参数为空\n");
return -1;
}
int i = find_by_value(l, old_value);
l->arr[i] = new_value;
return 0;
}
// 顺序表的按位置查找,返回对应的值
data find_by_index(list *l, int i)
{
if (NULL == l)
{
printf("传入参数为空\n");
return -1;
}
else if (i < 0 || i >= l->index)
{
printf("传入的位置不在合法区间\n");
return -1;
}
return l->arr[i];
}
// 顺序表的选择排序
int select_sort(list *l)
{
if (NULL == l)
{
printf("传入参数为空\n");
return -1;
}
for (int i = 0; i < l->index - 1; i++)
{
int max = i;
for (int j = i; j < l->index; j++)
{
if (l->arr[max] < l->arr[j])
{
max = j;
}
}
data temp = l->arr[max];
l->arr[max] = l->arr[i];
l->arr[i] = temp;
}
return 0;
}
// 顺序表的冒泡排序
int bubble_sort(list *l)
{
if (NULL == l)
{
printf("传入参数为空\n");
return -1;
}
for (int i = 0; i < l->index; i++)
{
for (int j = 0; j < l->index - i - 1; j++)
{
if (l->arr[j] > l->arr[j + 1])
{
data temp = l->arr[j];
l->arr[j] = l->arr[j + 1];
l->arr[j + 1] = temp;
}
}
}
return 0;
}
// 输出顺序表
int output(list *l)
{
if (NULL == l)
{
printf("传入参数为空\n");
return -1;
}
if (is_empty(l))
{
printf("是个空表,没有输出\n");
return -1;
}
for (int i = 0; i < l->index; i++)
{
printf("L->arr[%d]=%d\n", i, l->arr[i]);
}
return 0;
}
// 头删
int head_del(list *l)
{
if (NULL == l)
{
printf("传入参数为空\n");
return -1;
}
if (is_empty(l))
{
printf("是个空表,无法删除\n");
return -1;
}
for (int i = 0; i < l->index - 1; i++)
{
l->arr[i] = l->arr[i + 1];
}
l->index--;
return 0;
}
// 尾删
int tail_del(list *l)
{
if (NULL == l)
{
printf("传入参数为空\n");
return -1;
}
if (is_empty(l))
{
printf("是个空表,无法删除\n");
return -1;
}
l->index--;
return 0;
}
// 指定下标插入
int inset_by_index(list *l, int index, data value)
{
if (NULL == l)
{
printf("传入参数为空\n");
return -1;
}
else if (is_full(l))
{
printf("这个顺序表满了,无法继续插入\n");
return -1;
}
// 此处的 "index > l->index-1" 表示不能插到最后面,只能在最后一个数的前面插入
if (index <= 0 || index > l->index - 1)
{
printf("传入的位置不在合法区间\n");
return -1;
}
for (int i = l->index; i > index - 1; i--)
{
l->arr[i] = l->arr[i - 1];
}
l->arr[index] = value;
l->index++;
return 0;
}
// 指定下标删除
int del_by_index(list *l, int index)
{
if (NULL == l)
{
printf("传入参数为空\n");
return -1;
}
else if (is_empty(l))
{
printf("这个是个空顺序表,无法进行删除\n");
return -1;
}
if (index < 0 || index > l->index - 1)
{
printf("传入的位置不在合法区间\n");
return -1;
}
// 若删除最后一位的元素,则不会进入此循环,会直接将 L.index 减1
for (int i = index; i < l->index - 1; i++)
{
l->arr[i] = l->arr[i + 1];
}
l->index--;
return 0;
}
// 顺序表去重
int remove_duplicates(list *l)
{
if (NULL == l)
{
printf("传入参数为空\n");
return -1;
}
if (is_empty(l))
{
printf("是个空表,无法操作\n");
return -1;
}
for (int i = 0; i < l->index - 1; i++)
{
for (int j = i + 1; j < l->index; j++)
{
if (l->arr[i] == l->arr[j])
{
del_by_index(l, j);
j--;
}
}
}
return 0;
}
// 释放顺序表的空间并将其指向NULL
int free_list(list **l)
{
if (NULL == l)
{
printf("传入参数为空\n");
return -1;
}
free(*l);
*l = NULL;
return 0;
}
测试函数 : main.c
#include "head.h"
int main()
{
list *L = init();
head_insert(L, 20);
tail_insert(L, 22);
head_insert(L, 25);
tail_insert(L, 21);
head_insert(L, 20);
tail_insert(L, 22);
head_insert(L, 25);
/*
tail_insert(L, 21);
head_insert(L, 20);
tail_insert(L, 22);
head_insert(L, 25);
tail_insert(L, 21);
*/
output(L);
remove_duplicates(L);
output(L);
inset_by_index(L,3,10000);
output(L);
del_by_index(L,3);
output(L);
printf("22的下标为%d\n", find_by_value(L, 22));
printf("下标为1对应的值为%d\n", find_by_index(L, 1));
if (is_empty(L))
{
printf("是空表\n");
}
else
{
printf("不是空表\n");
}
if (is_full(L))
{
printf("是满表\n");
}
else
{
printf("不是满表\n");
}
change_by_value(L, 22, 2222);
output(L);
free_list(&L);
/*
printf("头删\n");
head_del(L);
output(L);
printf("尾删\n");
tail_del(L);
output(L);
printf("尾删\n");
tail_del(L);
output(L);
printf("尾删\n");
tail_del(L);
output(L);
printf("尾删\n");
tail_del(L);
output(L);
printf("尾删\n");
tail_del(L);
output(L);
printf("尾删\n");
tail_del(L);
output(L);
printf("尾删\n");
tail_del(L);
output(L);
printf("尾删\n");
tail_del(L);
output(L);
printf("尾删\n");
tail_del(L);
output(L);
printf("尾删\n");
tail_del(L);
output(L);
printf("尾删\n");
tail_del(L);
output(L);
*/
return 0;
}
工程管理文件 : makefile
EXE=fun
CC=gcc
CFLAGs=-c
OBJs+=main.o
OBJs+=list.o
all:$(EXE)
$(EXE):$(OBJs)
$(CC) $^ -o $@
%.o:%.c
$(CC) $(CFLAGs) $^ -o $@
clean:
rm *.o fun