1、什么是数据结构
数据结构研究计算机数据间关系,包括数据的逻辑结构和存储结构及其操作
2、基本概念:
1.数据:数据即信息的载体,是能够输入到计算机中并且能被计算机识别、存储和处理的符号总称。
2.数据元素:数据元素是数据的基本单位,又称之为记录。一般来说数据元素由若干基本项组成
3.数据的逻辑结构:表示数据运算之间的抽象关系
按每个元素可能具有的直接前趋数和直接后继数将逻辑结构分为线性结构和非线性结构两大类。
数据:数据即信息的载体,是能够输入到计算机中并且能被计算机识别、存储和处理的符号总称。
数据元素:数据元素是数据的基本单位,又称之为记录。一般来说数据元素由若干基本项组成
数据的逻辑结构:表示数据运算之间的抽象关系
按每个元素可能具有的直接前趋数和直接后继数将逻辑结构分为线性结构和非线性结构两大类。
线性结构——— 一个对一个,如线性表、栈、队列
树形结构——— 一个对多个,如树
图状结构——— 多个对多个,如图
3、数据的存储结构
存储结构:逻辑结构在计算机中的具体实现方法,存储结构是通过计算机语言所编制的程序来实现,因而是依赖于具体的计算机语言。
存储结构有四种,分别是:
第一:顺序存储(sequential storage)
将数据结构中各元素按照其逻辑顺序存放于存储器一片连续的存储空间中
如C语言的一维数组里面的元素。
第二:链式存储(重点)
将数据结构中各元素分布到存储器的不同点,用地址(或链指针)方式建立它们之间的联系。
数据结构中元素之间的关系在计算机内部很大程度上是通过地址或者指针来实现的。
第三:索引存储
在存储数据的同时,建立一个附加的索引表,即索引存储结构=数据文件+索引表。
第四:散列存储
根据数据元素的特殊字段(称为关键字key),计算数据元素的存放地址,然后数据元素按地址存放。
如下图:
4.线性表之顺序表
线性表是包含若干数据元素的一个线性序列
记为:L=(a0,a1,…,ai-1,ai,ai+1…)
L为表名,ai为数据元素
n为表长,n>0时,线性表L为非空表,否则为空表
线性表的特征有三个,分别是
1)对非空表表头无前驱;
2)对非空表表尾无后继
3)其他的每个元素ai有且仅有一个直接前驱ai-1和一个直接后继ai+1
顺序存储的优点是:逻辑上相邻的元素,ai,ai+1,其存储位置也是相邻的。对数据元素ai的存取为随机存取或按地址存取。存储密度高
存储密度D=(数据结构中元素所占存储空间)/整个数据结构所占的空间
顺序存储的不足:对表的插入和删除等运算的时间复杂度较差
线性表的顺序存储经常被人们称为顺序表
线性表的链式存储叫链表
在C语言中,可借助一维数组类型来描述线性表的顺序存储结构
4、具体的顺序表的编程实现
线性表的一些操作有:
1.建立一个线性表
2.线性表置空
3.判断是否是空表
4.求表长
5.。。。
每部分的代码如下:
4.1 list_create();过程如下几个步骤
1.定义一个结果体指针类型的变量L
2.申请内存
3.判断是否申请成功
4.初始化(用memset函数),memset函数里面的三个参数,其中L是需要填充的起始地址,0 是需要 填充的内容,sizeof(sqlist)是需要填充的字节大小
5.返回一个线性表的头L
4.2 list_insert(sqlink L,int value,int pos)过程
1.首先判断线性表是否满了
2.能插入的位置是【0,last+1】
3.元素的移动
4.存新值(last++)
sqlink.h文件内容
typedef int data_t;
#define N 128
typedef struct sqlist_t
{
data_t data[N];
int last;//最后一个元素下标
}sqlist,*sqlink;
sqlink list_create();//创建一个线性表
int list_clear(sqlink L);//清空线性表
int list_delete(sqlink L);//删除表
int list_empty(sqlink L);//判断线性表是否为空
int list_length(sqlink L);//求表长
int list_locate(sqlink L,data_t value);//查找某个元素是否在表中
int list_insert(sqlink L, data_t value, int pos);//在pos这个位置插入value
int list_show(sqlink L);//线性表的遍历
sqlink.c文件内容(具体的函数实现过程)
#include<stdio.h>
#include<string.h>
#include "sqlist.h"
#include<stdlib.h>
sqlink list_create()//创建一个线性表
{
//1.申请内存
sqlink L;
L = (sqlink)malloc(sizeof(sqlist));//申请内存
if (L == NULL)
{
printf("list malloc failed\n");
return 0;
}
//2.初始化
memset(L,0,sizeof(sqlist));
L->last = -1;//数组的最后一个元素的下标为-1,代表空值
//3/返回值
return L;
}
int list_clear(sqlink L)//清空线性表
{
if (L == NULL)//首先判断传过来的线性表是否为空表
{
return -1;//-1代表失败,0代表成功
}
//如果不是空表
memset(L, 0, sizeof(sqlist));
L->last = -1;//数组的最后一个元素的下标为-1,代表空值
return 0;
}
int list_delete(sqlink L)//删除表
{
if (L == NULL)
{
return -1;
}
free(L);
L = NULL;
return 0;
}
int list_empty(sqlink L)//检查线性表是否为空
{
if (L->last == -1)
{
return 1;//1代表为空
}
else
return 0;//0代表非空
}
int list_length(sqlink L)//计算线性表的长度
{
if (L == NULL)
{
return -1;
}
return (L->last + 1);
}
int list_insert(sqlink L, data_t value, int pos)
{
int i;
//1.检查线性表满?
if(L->last == N - 1)
{
printf("list is full\n");
return -1;
}
//2.检查需要插入的位置[0,last+1]
if (pos<0 || pos>L->last + 1)
{
printf("pos is invalue\n");
return -1;
}
//3.移动(最后面的数先后移)
for (i = L->last; i >= pos; i--)//i是下标
{
L->data[i + 1] = L->data[i];//元素后移操作
}
//4.更新(last++)
L->data[pos] = value;//将value放入pos
L->last++;//最后一个last加1
return 0;
}
int list_show(sqlink L)//线性表的遍历
{
int i;
if (L == NULL)//判断传过来的表是否有问题
{
return -1;
}
if (L->last == -1)
printf("list is empty\n");
for (i = 0; i <=L-> last; i++)
{
printf("%d ",L->data[i]);
}
puts("");
}
test.c文件内容
#include<stdio.h>
#include<string.h>
#include "sqlist.h"
#include<stdlib.h>
void test_insert();
int main()
{
test_insert();
return 0;
}
void test_insert()//封装成了一个插入函数
{
sqlink L;
L = list_create();
if (L == NULL)
{
return ;
}
list_insert(L, 10, 0);
list_insert(L, 20, 0);
list_insert(L, 30, 0);
list_insert(L, 40, 0);
list_insert(L, 50, 0);
list_insert(L, 60, 0);
list_show(L);
list_delete(L);
}
5、链表(单向链表)
链表是有一些列的节点组成,每个节点都包含两个域,一个是指针域,一个是数据域,其中指针域里面存的是下一个节点的地址。这里每个节点画了三个部分,是为了便于理解,具体的图如下:
其中里面的H是链表的头节点 ,其中头节点中数据域一般不存数据,或者数据为0
注意:H是头节点,也就是头节点的地址,H->next代表的是下一个节点的地址,最后一个节点的next是为NULL, 空链表和H==NULL是不一样的
链表的一些基本操作
5.1 链表的定义
typedef int data_t;
typedef struct node
{
data_t data;
struct node *next;//节点指针
}listnode,*linklist;//结构体名是listnode,结构体指针是linklist
5.2 链表的创建
linklist list_create();过程
1.先定义一个头指针
2.申请内存
3.为这个头节点赋初始值
4.返回
当然这里面的赋值仍然可使用memset函数
具体代码如下:
linklist list_create()//创建一个链表。返回值是一个指针
{
linklist H;
H = (linklist)malloc(sizeof(listnode));//动态申请空间
if (H == NULL)
{
printf("malloc failed\n");
return H;
}
H->data = 0;//第一个元素是0,这是赋初值
H->next = NULL;//第一个元素的next是NULL
return H;
}
5.3 尾部插入
1.定义一个新节点(需要插入链表的尾部)
2.为这个新结点申请内存
3.为这个新结点赋值
4.找这个链表的尾结点
5.将这个新结点插入到链表里面
具体代码如下:
int list_tail_insert(linklist H, data_t value)//尾部插入,参数1,H是链表,参数2,value是插入的值
{
linklist p;
linklist q;
if (H == NULL)
{
printf("H is NULL\n");
return -1;//若为空表返回就是-1
}
if ((p = (linklist)malloc(sizeof(listnode))) == NULL)//申请的一个新结点p
{
printf("malloc failed\n");
return -1;//申请失败也是返回-1
}
p->data = value;//p是一个插入的新节点,值就是value
p->next = NULL;//p是插入的一个新结点,p的next为空,
//找最后一个节点,最后一个节点的next为NULL
q = H;//q是要移动的节点,从H开始
while (q->next != NULL)//q的next非空
{
q = q->next;//q指针往后移,相当于循环输入
}
q->next = p;//将新节点p和头结点连接起来
return 0;
}
5.4 链表元素的遍历
1.先定义一个需要移动的节点指针p
2.让p开始往后移动
3.打印出每个元素
具体代码如下:
int list_show(linklist H)
{
linklist p;
if (H == NULL)
{
printf("H is NULL\n");
return -1;
}
p = H;
while (p->next != NULL)
{
printf("%d ",p->next->data);
p = p->next;
}
puts("");
return 0;
}
5.4 链表的删除(删除链表里面的某个元素)
先找到需要删除节点的前驱
最重要的是p->next=p->next->next
需要一个变量存储需要删除的节点,然后free掉
具体代码如下:
int list_delete(linklist H, int pos)//删除某个位置的节点
{
linklist p;//需要删除的节点的前驱用p存储
linklist q;//存储被删除的节点
//1、检查参数
if (H == NULL)
{
printf("H is NULL\n");
return -1;//若为空表返回就是-1
}
//2、找到插入位置
p = list_get(H,pos-1);
if (p == NULL)
return -1;
if (p->next == NULL)//这是最后一个节点的next为空,也就删除不了了,因为后面没元素了
{
printf("delete pos is invalid\n");
return -1;
}
//3、更新链表
q = p->next;//先将需要删除的指针存起来
p->next = q->next;//p->next=p->next->next
//4、释放free
printf("free:%d\n",q->data);
free(q);
q = NULL;
return 0;
}
5.6 删除链表里的所有元素
1.需要找一个需要移动的指针p,用来存储待删除的节点
为什么返回值是一个linklist 的指针而不是int类型,这是防止有些人已经把链表free了,但是又进行了一系列的其他操作,比如删除某个元素等等。
具体代码如下:
其中free(p)下面的代码H=H->next这句代码是多余的,加上之后会出现问题,
linklist list_free(linklist H)//删除掉所有的节点
{
linklist p;
if (H == NULL)
return NULL;
p = H;//让H去循环
printf("free:");
while (H != NULL)
{
p = H;
printf("%d ",p->data);
free(p);
H = H->next;
}
puts("");
//H = NULL;这句代码没有用,这是因为H是形参,形参的值不影响实参,可以修改返回值的类型为linklist
return NULL;
加上之后的运行结果如下:
5.7 链表的倒置(将单链表H倒置,不能有新结点的出现,只通过改变指针的指向
第一步:需要考虑以下三种特殊情况
1.H=NULL?(就是没有链表)
2.H就是一个空链表
3.只有一个节点
第二步:将链表一分为二
链表断开时,需要先找一个指针p用来指向断开后的第一个节点(为了防止断开后的元素丢失)
具体代码如下:
int list_reverse(linklist H)//链表的反转
{
linklist p;
linklist q;
//三种特殊情况
if (H == NULL)//链表不存在的情况
{
printf("H is NULL");
}
if (H->next == NULL || H->next->next == NULL)//空链表或者是只有一个节点
{
return 0;
}
//将链表一分为二
p = H->next->next;//p指向第二个节点
H->next->next = NULL;//链表一分为二
//循环进行,取出一个元素就插入头节点后面
while (p != NULL)
{
q = p;
p = p->next;//p后移
//以下两部先后顺序不能乱不能乱
q->next = H->next;//q放到H后
H->next = q;
}
return 0;
}
5.8 两个有序链表的合并(升序)
具体的图示如下:
步骤如下:
1.找两个指针,分别指向头节点的next
2.将头节点的next置空
3.循环比较两个链表头节点之后的元素的大小
具体的代码如下:
int list_merge(linklist H1, linklist H2)//有序链表的合并
{
linklist p, q, r;
if (H1 == NULL || H2 == NULL)
{
printf("H1||H2 is NULL\n");
return -1;
}
p = H1->next;
q = H2->next;
r = H1;
H1->next = NULL;
H2->next = NULL;
while (p && q)
{
if (p->data <= q->data)//比较两个数的大小
{
r->next = p;
p = p->next;
r = r->next;
r->next = NULL;
}
else
{
r->next = q;
q = q->next;
r = r->next;
r->next = NULL;
}
}
if (p == NULL)
{
r->next = q;
}
else
{
r->next = p;
}
return 0;
}
5.9 链表的排序(从小到大)
首先将链表一分为二,然后循环比较元素的大小,最后将小的元素插入到头节点的后面
具体代码如下:
int list_paixu(linklist H)//链表从小到大排序
{
linklist p, q, r;
p = H->next;
H->next = NULL;//链表一分为二
while (p)
{
q = p;
p = p->next;//p指针后移
r = H;
while (r->next && r->next->data < q->data)
r = r->next;
q->next = r->next;
r->next = q;
}
}