今天开始,我们就来学习数据结构了。
数据结构的概念
数据结构是计算机存储,组织数据的方式。
数据结构是指相互之间存在一种或多种特定关系的数据元素
的集合。通常情况下,精心选择的数据结构 可以带来更高的运行或者存储效率。
名词解释
数据
-
程序的操作对象。用于描述客观事物。
数据是一个抽象的概念, 将其进行分类后得到程序设计语言中的类型。如
:int , float , char
等等。
数据是对客观事物的符号表示,在计算机科学中是指所有能输入到计算机中被计算机程序处理的符号的 总称。
数据元素
:
组成数据的基本单位,在计算机程序中通常作为一个整体进行考虑和处理。
数据项
:
一个数据元素由若干数据项组成。
例:一本书的信息为一个数据元素,而数目信息中的每一项(如书名,作者名等)为一个数据项
数据对象
-
性质相同的数据元素的集合
(
比如
:
数组
,
链表
)。
![](https://img-blog.csdnimg.cn/7caa732fd81f467f860d6c36104f07a2.png)
//声明一个结构体类型
struct MyTeacher //-种数据类型
{
char name[32];
int id;
int age;
char addr[128];
}
int main
{
struct MyTeacher tech01; //数据元素
struct MyTeacher tArray[30]; //数据对象
memset(&t1, 0, sizeof(tech01));
strcpy(tech01.name,"大明");
strcpy(tech01.addr, "西安");
tech01.id = 1001;
tech01.age = 20;
}
数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。数据元素都不是孤立存在的,而是在它们之间存在着某种关系,这种数据元素相互之间的关系称为结构。根据数据元素之间关系的不同 特性,分为4中基本结构:
(1)集合:结构中的数据元素之间除了
"
同属于一个集合
"
的关系外,别无其他关系;
(2)线性结构:结构中的数据元素之间存在一个对一个的关系;
(3)树形结构:结构中的数据元素之间存在一个对多个的关系;
(4)图状结构:结构中的数据元素之间存在多个对多个的关系。
线性表
线性表是零个或多个数据元素的有限序列。
特性:
数据元素之间是有顺序的。
数据元素个数是有限的。
数据元素的类型必须相同。
总结:线性表是具有相同类型的
n( >= 0)
个数据元素的有限序列
( a0,a1, a2, ... an )
ai
是表项,
n
是表长度。
n=0
时称为空表,在非空表中的每个数据元素都有一个确定的位置,
ai
是第
i
个数据元素,称i
为数据元素
ai
在线性表中的位序。
线性表是一个相当灵活的数据结构,他的长度可根据需要增长或缩短,即对线性表的数据元素不仅可以进行访问,还可进行插入和删除等。
数据元素之间的关系在计算机中有四种不同的存储结构:
顺序存储结构、链式存储结构
、索引存储结构和散列存储结构。
特点:
在数据元素的非空有限集合中,
➢
a0
为线性表的第一个元素
,
只有一个后继。
➢
an
为线性表的最后一个元素
,
只有一个前驱。
➢
除
a0
和
an
外的其它元素
ai,
既有前驱,又有后继。
➢
线性表能够逐项访问和顺序存取。
线性表的操作
➢
创建线性表
➢
销毁线性表
➢
清空线性表
➢
将元素插入线性表
➢
将元素从线性表中删除
➢
获取线性表中某个位置的元素
➢
获取线性表的长度
➢
判断线性表是否为空
线性表的顺序存储
线性表的顺序存储结构
,
指的是用一段地址连续的存储单元依次存储线性表的数据元
素。
线性表
(a1, a2, ..... an )
的顺序存储示意图如下
:
![](https://img-blog.csdnimg.cn/3ac41559abc147baae9ba909989f7dc4.png)
线性表顺序存储的设计与实现
操作要点
:
●
插入元素算法
■判断线性表是否合法
■判断插入位置是否合法
■把最后一个元素到插入位置的元素后移一个位置
■将新元素插入
■线性表长度加
1
●
获取元素操作
■判断线性表是否合法
■判断位置是否合法
■直接通过数组下标的方式获取元素
●
删除元素算法
■判断线性表是否合法
■判断删除位置是否合法
■将元素取出
■将删除位置后的元素分 别向前移动一个位置
■线性表长度减
1
优点和缺点
➢
优点
:
●
无需为线性表中的逻辑关系增加额外的空间。
●
可以快速的获取表中合法位置的元素。
➢
缺点
:
●
插入和删除操作需要移动大量元素。
●
当线性表长度变化较大的时候
,
难以确定存储空间的容量。
线性表顺序存储案例
1
当插入一个新的元素的时候,这个时候发现空间不足
?
申请一块更大的内存空间。
2
将原空间的数据拷贝到新的空间
3
释放旧的空间
4
把元素放入新的空间
重点函数讲解:
1.
初始化表
![](https://img-blog.csdnimg.cn/3022aa2783d0496995d00a65e8e5c9cb.png)
2. 插入数据
main.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "dynamic_arr.h"
int main()
{
//初始化动态数组
DYNAMIC_ARR *my_array=init_arr();
//插入元素
for(int i=0;i<30;i++)
{
insert_arr(my_array, i+100);
}
//打印容量
printf("数组容量:%d\n",get_capacity_arr(my_array));
printf("数组元素个数:%d\n",get_size_arr(my_array));
//打印
print_arr(my_array);
//销毁
free_arr(my_array);
return 0;
}
dynamic_arr.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include "dynamic_arr.h"
//初始化数组
DYNAMIC_ARR *init_arr(void)
{
DYNAMIC_ARR *parr= (DYNAMIC_ARR *)malloc(sizeof(DYNAMIC_ARR));
if(parr==NULL)
{
printf("malloc failed\n");
return NULL;
}
parr->size=0;
parr->capacity=20;
parr->paddr=(int *)malloc(sizeof(int)*parr->capacity);
if(parr->paddr==NULL)
{
printf("malloc failed\n");
return NULL;
}
return parr;
}
//尾插法:插入元素
void insert_arr(DYNAMIC_ARR *arr, int value)
{
if(arr == NULL)
return;
//判断空间是否足够
if(arr->size == arr->capacity)
{
//1.申请更大空间,新空间是旧空间2倍
int *new_space=malloc(sizeof(int)*arr->capacity*2);
//2.拷贝数据到新空间
memcpy(new_space,arr->paddr,arr->capacity*sizeof(int));
//3.更新容量
arr->capacity=arr->capacity*2;
arr->paddr=new_space;
}
//插入新元素
arr->paddr[arr->size]=value;
arr->size++;
}
//依据位置删除元素
int del_by_pos(DYNAMIC_ARR *arr, int pos)
{
if(arr == NULL)
{
return -1;
}
//判断位置是否有效
if(pos<0 || pos >= arr->size)
{
return -1;
}
int i;
int del_val;
del_val = arr->paddr[pos];
for(i=pos;i<arr->size;i++)
{
arr->paddr[i]=arr->paddr[i+1];
}
arr->size--;
return del_val;
}
//依据数据查找出现的位置
int find_by_value(DYNAMIC_ARR *arr, int value)
{
if(arr == NULL)
{
return -1;
}
//查找
int i;
int pos=-1;
for(i=0;i<arr->size;i++)
{
if(arr->paddr[i]==value)
{
pos=i;
break;
}
}
return pos;
}
//依据值删除元素
void del_by_value(DYNAMIC_ARR *arr, int value)
{
if(arr == NULL)
{
return;
}
//找到值的位置
int i;
int pos=find_by_value(arr,value);
//根据位置删除
del_by_pos(arr,pos);
}
//根据位置获得某个元素值
int find_by_pos(DYNAMIC_ARR *arr, int pos)
{
if(arr == NULL)
{
return -1;
}
return arr->paddr[pos];
}
//打印
void print_arr(DYNAMIC_ARR *arr)
{
if(arr == NULL)
{
return;
}
int i;
for(i=0;i<arr->size;i++)
{
printf("%d ",arr->paddr[i]);
}
printf("\n");
}
//释放动态数组内存
void free_arr(DYNAMIC_ARR *arr)
{
if(arr == NULL)
{
return;
}
if(arr->paddr != NULL)
{
free(arr->paddr);
}
free(arr);
}
//获取动态数组容量
int get_capacity_arr(DYNAMIC_ARR *arr)
{
if(arr == NULL)
{
return -1;
}
return arr->capacity;
}
//获取动态数组当前存储元素个数
int get_size_arr(DYNAMIC_ARR *arr)
{
if(arr == NULL)
{
return -1;
}
return arr->size;
}
dynamic_arr.h
#ifndef _DYNAMIC_ARR_H_
#define _DYNAMIC_ARR_H_
typedef struct DYNAMIC
{
int *paddr; //指向存储数据的指针
int size; //数组已存储元素个数
int capacity; //数组容量大小
}DYNAMIC_ARR;
//初始化数组
DYNAMIC_ARR *init_arr(void);
//插入元素
void insert_arr(DYNAMIC_ARR *arr, int value);
//依据位置删除元素
int del_by_pos(DYNAMIC_ARR *arr, int pos);
//依据值删除元素
void del_by_value(DYNAMIC_ARR *arr, int value);
//依据数据查找
int find_by_value(DYNAMIC_ARR *arr, int value);
//打印
void print_arr(DYNAMIC_ARR *arr);
//释放动态数组内存
void free_arr(DYNAMIC_ARR *arr);
//获取数组容量
int get_capacity_arr(DYNAMIC_ARR *arr);
//获取元素个数
int get_size_arr(DYNAMIC_ARR *arr);
#endif
线性表的链式存储
基本概念
链表是一种物理
存储单元
上非连续、非顺序的
存储结构
,
数据元素
的逻辑顺序是通过链表中的
指针
链接 次序实现的。
链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两 个部分:一个是存储
数据元素
的数据域,另一个是存储下一个结点地址的
指针
域。
数据域:保存数据的地方 (当前节点所需要保存的数据)
指针域:保存地址的地方(是用来保存下一个节点的地址)
一般链表的
头节点的数据域
不存放任何数据,这是为了链表方便操作。
![](https://img-blog.csdnimg.cn/bcc28069b42d4a0299295a1804c97c9c.png)
例程:静态链表创建
listnode.c
![](https://img-blog.csdnimg.cn/e643b767993f413abe2c065099fb33cb.png)
#include <stdio.h>
#include <stdlib.h>
//定义一个data结构体
typedef struct //一种数据类型
{
char name[32];
int id;
char addr[128];
}Tech_info;
//定义一个结点结构体
typedef struct node
{
Tech_info data; //数据域
struct node *next; //指针域
}Node;
int main()
{
//定义并初始化三个数据
Tech_info p1={"刘德华",1002,"香港"};
Tech_info p2={"郭德纲",1001,"天津"};
Tech_info p3={"王宝强",1003,"河北"};
//定义结点
Node *node1=(Node *)malloc(sizeof(Node));
if(node1==NULL)
{
printf("malloc failed\n");
}
Node *node2=(Node *)malloc(sizeof(Node));
if(node2==NULL)
{
printf("malloc failed\n");
}
Node *node3=(Node *)malloc(sizeof(Node));
if(node3==NULL)
{
printf("malloc failed\n");
}
//初始化结点数据域
node1->data=p1;
node2->data=p2;
node3->data=p3;
//初始化结点指针域,实现三个结点进行连接
node1->next=node2;
node2->next=node3;
node3->next=NULL;
printf("p3.name=%s\n",node1->next->next->data.name);
printf("p3.name=%s\n",node3->data.name);
return 0;
}
●单链表
■
线性表的链式存储结构中
,
每个节点中只包含一个指针域
,
这样的链表叫单链表(即构成链表的每个 结点只有一个指向直接后继结点的指针)
■
通过每个节点的指针域将线性表的数据元素按其逻辑次序链接在一 起
(
如图
)。
![](https://img-blog.csdnimg.cn/879d9bb541dd4f91b9ba2d6cafd2c3e6.png)
●
双链表
■
线性表的链式存储结构中
,
每个节点中包含两个指针域。
■
指针域分别指向直接后继和直接前驱。
![](https://img-blog.csdnimg.cn/854c33b494e24089be77070b61e52112.png)
●
单向循环链表
■
线性表的链式存储结构中
,
每个节点中只包含一个指针域。
■
表中最后一个结点的
指针
域指向
头结点
,整个链表形成一个环。
![](https://img-blog.csdnimg.cn/6fc1fb39d8d943da87052997030677b4.png)
概念解释
链表中的第一个结点
,
包含指向第一个数据元素的指针以及链表自身的一
-
些信息
■
数据结点
链表中代表数据元素的结点
,
包含指向下一个数据元素的指针和数据元素的信息
■
尾结点
链表中的最后一个数据结点
,
其下一元素指针为空
,
表示无后继。
![](https://img-blog.csdnimg.cn/d947799a4f63438c883790582258cb84.png)
优点和缺点
➢
优点
:
■
无需一次性定制链表的容量
插入和删除操作无需移动数据元素
➢
缺点
:
数据元素必须保存后继元素的位置信息
获取指定数据的元素操作需要顺序访问之前的元素
链表和数组
链表和数组
一样
,
可以用于存储一系列的元素
,
但是链表和数组的实现机制完全不同。
数组:
要存储多个元素,数组(或列表)可能是最常用的数据结构。
几乎每一种编程语言都有默认实现数组结构,
这种数据结构非常方便,提供了一个便利的 [] 语法来访问它的元素。
但是数组也有很多缺点
:
数组的创建通常需要申请一段连续的内存空间(
一整块的内存
),
并且大小是固定的
(
大多数编程
语言数组都是固定的),
所以当当前数组不能满足容量需求时
,
需要扩容
. (
一般情况下是申请一
个更大的数组,
比如
2
倍
.
然后将原数组中的元素复制过去
)
而且在数组开头或中间位置插入数据的成本很高,
需要进行大量元素的移动。
链表:
要存储多个元素,
另外一个选择就是使用链表。
但不同于数组,
链表中的元素在内存中不必是连续的空间。
链表的每个元素由一个存储元素本身的节点和一个指向下一个元素的指针组成。
相对于数组
,
链表有一些优点
:
内存空间不必连续的.
可以充分利用计算机的内存
.
实现灵活的内存动态管理。
链表不必在创建时就确定大小,
并且大小可以无限的延伸下去。
链表在插入和删除数据时,
相对数组效率高很多。
相对于数组
,
链表有一些缺点
:
链表访问任何一个位置的元素时,
都需要从头开始访问
.(
无法跳过第一个元素访问任何一个元
素)
。
无法通过下标直接访问元素,
需要从头一个个访问
,
直到找到对应的位置。
单向链表设计
例程:创建一个动态单向链表
1.
定义链表结点:数据域
+
指针域
2.
定义链表结构体:头结点指针
+
结点数
3.
初始化链表
4.
指定位置插入新数据
5.
删除指定位置数据
6.
获取链表长度
7.
依据数据查找所在链表位置
8.
返回第一个结点
9.
打印链表结点数据
10.
释放链表
例程:动态例程
linklist.c
、
linklist.h
、
main.c
添加结点
![](https://img-blog.csdnimg.cn/8386aa6a588949db82f575acfa29cfb3.png)
删除结点
linklist.c:实现链表的创建、插入、删除、查找、获取链表长度、释放操作
#include "linklist.h"
//初始链表
LinkList *init_linklist(void)
{
LinkList *list=(LinkList *)malloc(sizeof(LinkList));
list->size=0;
//头结点
list->head=(LinkNode *)malloc(sizeof(LinkNode));
list->head->data=NULL;
list->head->next=NULL;
return list;
}
//指定位置插入,z
void insert_linklist(LinkList *list, int pos, void *data)
{
int count;
if(list == NULL)
{
return ;
}
if(data == NULL)
{
return ;
}
if(pos<0 || pos >list->size)
{
pos=list->size;
}
//创建新的结点
LinkNode *newnode=(LinkNode *)malloc(sizeof(LinkNode));
newnode->data=data;
newnode->next=NULL;
//找结点
//辅助指针变量
LinkNode *pcurrent=list->head;
int i;
for(i=0;i<pos;i++)
{
pcurrent=pcurrent->next;
}
#if 0
while(pcurrent!=NULL&&pcurrent->next!=NULL)
{
pcurrent=pcurrent->next;
count++;
if(count=pos)
{
break;
}
}
#endif
//新结点插入链表
newnode->next=pcurrent->next;
pcurrent->next=newnode;
list->size++;
}
//删除指定位置的值
void del_by_pos_linklist(LinkList *list, int pos)
{
if(list == NULL)
{
return ;
}
if(pos<0 || pos >list->size)
{
return ;
}
//找结点
//辅助指针变量
LinkNode *pcurrent=list->head;
int i;
for(i=0;i<pos;i++)
{
pcurrent=pcurrent->next;
}
//删除结点
LinkNode *delnode=pcurrent->next;
pcurrent->next=delnode->next;
free(delnode);
list->size--;
}
//获取链表长度
int size_linklist(LinkList *list)
{
return list->size;
}
//查找
int find_linklist(LinkList *list, void *data)
{
if(list == NULL)
{
return -1;
}
if(data == NULL)
{
return -2;
}
//遍历查找
LinkNode *pcurrent = list->head->next;
int i=1;
while(pcurrent!=NULL)
{
if(pcurrent->data==data)
{
break;
}
i++;
pcurrent=pcurrent->next;
}
return i;
}
//返回第一个结点
void *first_linklist(LinkList *list)
{
return list->head->next->data;
}
//打印链表结点
void print_linklist(LinkList *list, printlinknode print)
{
if(list == NULL)
{
return ;
}
//辅助指针变量
LinkNode *pcurrent=list->head->next;
while(pcurrent!=NULL)
{
print(pcurrent->data);
pcurrent=pcurrent->next;
}
}
//释放链表内存
void free_linklist(LinkList *list)
{
if(list == NULL)
{
return ;
}
//辅助指针变量
LinkNode *pcurrent=list->head;
while(pcurrent!=NULL)
{
//缓存下一个结点
LinkNode *pnext=pcurrent->next;
free(pcurrent);
pcurrent=pnext;
}
//释放链表内存
list->size=0;
free(list);
}
linklist.h
:用于声明
linklist.c
的所有函数声明
#ifndef LINKLIST_H
#define LINKLIST_H
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//链表节点
typedef struct NODE
{
void *data;
struct NODE *next;
}LinkNode;
//链表结构体
typedef struct
{
LinkNode *head;
int size;
}LinkList;
//打印函数指针
typedef void (*printlinknode)(void *);
//初始链表
LinkList *init_linklist(void);
//指定位置插入
void insert_linklist(LinkList *list, int pos, void *data);
//删除指定位置的值
void del_by_pos_linklist(LinkList *list, int pos);
//获取链表长度
int size_linklist(LinkList *list);
//依据数据查找所在位置
int find_linklist(LinkList *list, void *data);
//返回第一个结点
void *first_linklist(LinkList *list);
//打印链表结点
void print_linklist(LinkList *list, printlinknode print);
//释放链表内存
void free_linklist(LinkList *list);
#endif
main.c
:进行测试链表的所有操作。
#include "linklist.h"
typedef struct
{
char name[64];
int age;
int score;
}Person;
//打印函数
void my_print(void *data)
{
Person *p=(Person *)data;
printf("name:%s age:%d score:%d\n",p->name,p->age,p->score);
}
int main()
{
//创建链表
LinkList *list=init_linklist();
//获取链表长度
printf("list 长度:%d\n",size_linklist(list));
//创建数据
Person p1={"大明",18,96};
Person p2={"小明",19,97};
Person p3={"老明",20,98};
//数据插入链表
insert_linklist(list, 0, &p1);//0表示头插法
insert_linklist(list, 0, &p2);
//打印
print_linklist(list, my_print);
printf("\n");
//指定位置1处,插入
insert_linklist(list, 1, &p3);
//打印
print_linklist(list, my_print);
printf("\n");
//依据数据找所在位置
int ret=find_linklist(list, &p3);
printf("\"老明\"所在位置:%d\n",ret);
//指定位置1处,删除
del_by_pos_linklist(list, 1);
//打印
print_linklist(list, my_print);
printf("\n");
//获取链表长度
printf("list 长度:%d\n",size_linklist(list));
//返回第一个结点
Person *p=(Person *)first_linklist(list);
printf("第一个结点:name-%s,age-%d,score-%d\n",p->name,p->age,p->score);
//销毁
free_linklist(list);
return 0;
}
今天就学到这里,明天我们继续哦!