主要有以下几个内容:
1.线性表
2.顺序表
3.链表
4.顺序表和链表的区别
1.线性表
线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串、广义表…
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
线性表:它的数据呈现线性关系,不是内存上是连续的,是指的它逻辑上是连续的。
什么是逻辑?什么是物理?
数据结构主要分两种结构:
1.物理结构(内存中如何存):比如数组,在内存中开连续的空间,挨着保存。
2.逻辑结构(想象出来的)
线性表在物理上只要有两种:1.数组 2.链表
进程的内存需要分段管理:栈区,堆区,静态区(数据段),常量区(代码段)。
数组在这几个区域内都可以开辟空间,栈区(局部变量),堆区(malloc开辟),静态区(全局变量),常量区(常量字符串)。但是他们都有一个特点:在物理上是连续的。
数组在物理上和逻辑上都是连续的。
数组会有一种巨大的缺陷:**不知道需要存多少个, 存100个,但你想存101个的时候就存不下了,不能按需索取。
链式结构需要:结构体来实现。
2.顺序表
使用数组结构来存储数组,顺序表在本质上就是一个数组。
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
顺序表一般可以分为:
- 静态顺序表:使用定长数组存储。
- 动态顺序表:使用动态开辟的数组存储。
用三个文件实现:1.test.c 2.Seqlist.c 3.Seqlist.h
1.test.c :用来进行测试
//测试头尾插入删除
//#include<stdio.h>
//
计算斐波那契递归Fib的时间复杂度?
//long long Fib(size_t N)
//{
// if (N < 3)
// return 1;
//
// return Fib(N - 1) + Fib(N - 2);
//}
//
//long long Fac(size_t N)
//{
// if (0 == N)
// return 1;
//
// return Fac(N - 1)*N;
//}
//
//int main()
//{
// //printf("%d\n", Fib(30));
// //printf("%d\n", Fac(100000));
//
//
//
// return 0;
//}
#include"SeqList.h"
void TestSeqList1()
{
SLT s;
//SeqListInit(NULL);
SeqListInit(&s);
SeqListPushBack(&s, 1);
SeqListPushBack(&s, 2);
SeqListPushBack(&s, 3);
SeqListPushBack(&s, 4);
SeqListPushBack(&s, 5);
SeqListPrint(&s);
SeqListPushFront(&s, -1);
SeqListPrint(&s);
SeqListPopBack(&s);
SeqListPrint(&s);
SeqListDestory(&s);
}
int main()
{
TestSeqList1();
return 0;
}
2.Seqlist.c:结构体的实现
#include "SeqList.h"
void SeqListInit(SLT* psl)
{
assert(psl);
psl->a = NULL;
psl->size = psl->capacity = 0;
}
void SeqListDestory(SLT* psl)
{
assert(psl);
if (psl->a)
{
free(psl->a);
psl->a = NULL;
}
psl->size = psl->capacity = 0;
}
void SeqListPrint(SLT* psl)
{
assert(psl);
for (int i = 0; i < psl->size; ++i)
{
printf("%d ", psl->a[i]);
}
printf("\n");
}
void SeqListCheckCapcity(SLT* psl)
{
assert(psl);
// 是否满了,满了就要增容
if (psl->size == psl->capacity)
{
size_t newcapacity = psl->capacity == 0 ? 4 : psl->capacity * 2;
psl->a = realloc(psl->a, newcapacity*sizeof(SQDataType));
psl->capacity = newcapacity;
}
}
// 尾插尾删,头插头删
void SeqListPushBack(SLT* psl, SQDataType x)
{
assert(psl);
SeqListCheckCapcity(psl);
psl->a[psl->size] = x;
psl->size++;
}
void SeqListPushFront(SLT* psl, SQDataType x)
{
assert(psl);
SeqListCheckCapcity(psl);
// 挪动数据
int end = psl->size - 1;
while (end >= 0)
{
psl->a[end + 1] = psl->a[end];
--end;
}
psl->a[0] = x;
psl->size++;
}
void SeqListPopBack(SLT* psl)
{
assert(psl);
//psl->a[psl->size - 1] = 0;
psl->size--;
}
void SeqListPopFront(SLT* psl);
3.头文件(Seqlist.h):整个数据结构的声明,有哪些接口,有哪些函数。 将所有引用都放在.h文件夹里面,因为在其他两个文件中都会展开。
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
// 静态的顺序表结构
//#define N 1000
//typedef char SQDataType;
//
//typedef struct SeqList
//{
// SQDataType a[N];
// int size;
//}SLT;
//typedef struct SeqList SLT;
//typedef struct SeqList* PSLT;
// 驼峰 单词首字母大写
typedef int SQDataType;
typedef struct SeqList
{
SQDataType* a;
int size; // 有效数据的个数
int capacity; // 容量空间大小
}SLT;
// 增删查改
void SeqListInit(SLT* psl);
void SeqListDestory(SLT* psl);
void SeqListPrint(SLT* psl);
// 尾插尾删,头插头删
void SeqListPushBack(SLT* psl, SQDataType x);
void SeqListPushFront(SLT* psl, SQDataType x);
void SeqListPopBack(SLT* psl);
void SeqListPopFront(SLT* psl);
静态顺序表的头文件实现(大小不可变)
.h
#pragma once
#include <stdio.h>
#include <stdlib.h> //malloc在这个头文件之中
//放整个数据结构的声明(有哪些接口,有哪些函数)
//顺序表,有效数组在数组中必须是连续的
typedef int SLDataType; //只要将int 改成double,则整个数据结构的类型都会发生变换
#define N 10 //想要顺序表变成多大就改成多大
struct SeqList
{
SLDataType a[N];
int size;//存了多少个有限数据
};
void SeqListPushBack(struct SL* ps, SLDataType x);
void SeqListPopBack(struct SL* ps);
void SeqListPushFront(struct SL* ps, SLDataType x);
void SeqListPopFront(struct SL* ps);
//需要变double只需要将SLDataType变成double即可。
Vector原型:动态顺序表(大小可变)
实现尾插函数
size就是它的尾,size是前面数据的个数, 他也是最后一个有效数字的下一个下标。
//实现尾插函数void SeqListPushBack(SL* ps,SLDataType x)
void SeqListPushBack(SL* ps,SLDataType x)
{
assert(ps);//断言以下,如果传的是空指针,直接结束。
//如果满了需要扩容,但是桌面增加呢?比较通用的方法是增二倍
if(ps->size >= ps->capacity)
{
ps->capacity *= 2; //如果a=20,a*=6,那么结果就是a=120
ps->a = (SLDataType*)realloc(ps->a,sizeof(SLDataType)*ps->capacity);
//ps->a原来的空间,sizeof(SLDataType)*ps->capacity你要增加的空间。
if(ps->a==NULL)
{
printf("扩容失败\n");
exit(-1);
}
//size不动,增容数据个数不变,因为realloc会进行拷贝。
}
ps->a[ps->size]=x;
ps->size++;
}
实现尾删函数:
void SeqListPopBack(SL* ps)
{
assert(ps);
ps->a[pa->size-1]=0; //这句语句没有意义
ps->size--;
}
实现头插函数:
先定义一个下标end,指向这段空间的最后一个位置,end指向比0小的时候结束。
void SeqListPushFront(SL* ps,SLDataType x)
{
//只要是插入,一定要考虑空间不够了,得扩容。
int end = ps->size-1;
while(end >=0)
{
ps->a[end + 1]= ps->a[end];
--end;
}
ps->a[0] = x;
ps->size++;
}
SeqListPushFront(&s,-1);
只要是插入,一定要考虑空间不够了,得扩容。增加扩容接口,只要需要扩容,直接调用它即可。在头文件中加
void SeqListCheckCapacity(SL* ps)
{
if(ps->size >= ps->capacity)
{
ps->capacity *= 2; //如果a=20,a*=6,那么结果就是a=120
ps->a = (SLDataType*)realloc(ps->a,sizeof(SLDataType)*ps->capacity);
//ps->a原来的空间,sizeof(SLDataType)*ps->capacity你要增加的空间。
if(ps->a==NULL)
{
printf("扩容失败\n");
exit(-1);
}
//size不动,增容数据个数不变,因为realloc会进行拷贝。
}
}
实现头删函数:
void SeqListPopFront(SL* ps)
{
assert(ps);
int start = 0;
while (start < ps->size-1)
{
ps->a[start] = ps->a[start+1];
++start;
}
ps->size--;
}
顺序表看起来简单,其实顺序表就是保持线性表得特性,数据是连续得,并且它又摆脱了数组得限制,数组是固定大小的。
顺序表也有一些缺陷:
1.如果用的不好的话,会浪费空间。如果现在有100个数据,你需要101,那么你就需要扩容到200,则99个就浪费了。
2.它进行尾插,尾删快,但进行头插头删时,空间复杂度时o(N),因为需要移动数据。(链表可以进行解决)
链表虽然方便,但如果要进行排序,链表不好排序。二分查找,链表不能进行。链表的致命缺点就是不能进行随机访问。