线性表的基本概念
<1>定义
线性表是0个或者多个数据元素的集合
线性表中的数据元素之间是有序的
线性表中的数据元素个数是有限的
线性表中的数据元素类型必须相同
<2>性质
a0为线性表的第一个元素,只有一个后继
an为线性表的最后一个元素,只有一个前驱
除这二者外的其他元素ai,既有前驱,又有后继
线性表能够逐项和顺序存取
线性表的顺序存储结构
1.基本概念
线性表的顺序存储结构,指的是用一段地址连续的存储单元依次存储线性表的数据元素
2.设计与实现
在写程序之前,先说明一下单链表的头插法和尾插法
头插法:从一个空表开始,重复读入数据,生成新的结点,将读入的数据存放到新的结点的数据域中,然后将新结点插入到当前链表的表头结点之后。
尾插法:从一个空表开始,重复读入数据,生成新的结点,将读入的数据存放到新的结点的数据域中,然后将新结点插入到当前链表的表尾结点之后。
首先先创造一个整体的框架,先自定一个header文件,然后把想要实现的方法写入,然后直接在主程序中导入此头文件
header.h
#ifndef CPRIMERPLUS_HEADER_H
#define CPRIMERPLUS_HEADER_H
typedef void SeqList;//声明一下线性表
typedef void SeqListNode;//声明组成结点的结构体
SeqList *seqList_Create(int capacity);//创建一张线性表
void SeqList_Destroy(SeqList *list);//销毁一张线性表
void SeqList_Clear(SeqList *list);//清空结点元素
int SeqList_Length(SeqList *list);//返回线性表的长度
int SeqList_Insert(SeqList *list,SeqListNode *node,int pos);//向SeqList链表pos的位置插入一个结点node
SeqListNode *list_Get(SeqList *list , int pos);//获取pos位置元素
SeqListNode *list_Delete(SeqList *list,int pos);//删除pos位置元素
#endif //CPRIMERPLUS_HEADER_H
头文件总体的思路写好后,下面再把主程序中的使用写出来
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "header.h"
typedef struct Teacher{
int age;
char *name;
}Teacher;
int main(){
int ret = 0;
SeqList *list = NULL;
Teacher t1,t2,t3,t4,t5;
t1.age = 30;
t2.age = 31;
t3.age = 32;
t4.age = 33;
t5.age = 34;
list = seqList_Create(10);
ret = SeqList_Insert(list, (SeqListNode *) &t1, 0);//头插法
ret = SeqList_Insert(list, (SeqListNode *) &t2, 0);
ret = SeqList_Insert(list, (SeqListNode *) &t3, 0);
ret = SeqList_Insert(list, (SeqListNode *) &t4, 0);
ret = SeqList_Insert(list, (SeqListNode *) &t5, 0);
//遍历单链表,获取结点
for (int i = 0; i < SeqList_Length(list); ++i) {
Teacher *t = (Teacher *) list_Get(list, i);
if (t == NULL) {
return 0;
}
printf("t->age:%d", t->age);
}
//删除链表中的结点
for (int j = 0; j < SeqList_Length(list); ++j) {
list_Delete(list, 0);
}
system("pause");
}
然后最后再把细节写好,主要采用的还是头插法插入。
在同一个directory下创建具体方法的实现即可。
思路:
1.先把链表的结构写好,可以用struct,也可以用typedef struct起别名
typedef struct SeqList{
int length;//链表实际长度
int capacity;//分配最大的容量
unsigned int *node;//所有的结点所形成的数组
// (动态库内的结点,有多少分配多少,是动态的,所以要用指针指向内存空间内的结点)
}tSeqList;
2.在链表的create方法中,先让链表初始空间为Nul,然后为其动态分配存储空间,因为我们是动态链表,然后分配的类型和node的类型一致即可,分配完链表的空间后,为分配好的空间引入memset方法
void *memset(void *s,int c,size_t n)
总的作用:将已开辟内存空间s的首n个字节的值设为值c。
然后根据capacity分配node的空间,方法都是一样的
最后去给容量和结点赋初值,第一个函数也就写完了
SeqList *seqList_Create(int capacity){
tSeqList *tmp = NULL;//既可以在栈分配内存空间,也可以在堆分配内存空间,但是让初始的链表为空
tmp = (tSeqList *) malloc(sizeof(tSeqList));
if(tmp ==NULL) {
printf("func SeqList_Create error:%d",0);
return NULL;
}
memset(tmp, 0, sizeof(tSeqList));//总的作用:将已开辟内存空间tmp的首sizeof(tSeqList)个字节的值设为值0
// 根据capacity分配结点空间
tmp->node = (unsigned int *) malloc(sizeof(unsigned int *) * capacity);
if (tmp->node == NULL) {
printf("func SeqList_Create error:%d",0);
return NULL;
}
tmp->capacity = capacity;
tmp->length = 0;
return tmp;
}
3.下面我们开始写destroy函数,因为我们之前开辟了两次内存空间(seqlist和node),所以也应该是先释放后开辟的空间
void SeqList_Destroy(SeqList *list){
tSeqList *tList = NULL;
if (list == NULL) {
return;
}
tList = (tSeqList *) list;
if (tList->node != NULL) {
free(tList->node);
}
free(tList);
}
4.然后写clear清空链表函数,但是不是销毁free掉,而是让链表长度回归为0
//清空链表
void SeqList_Clear(SeqList *list){
tSeqList *tList = NULL;
if (list == NULL) {
return;
}
tList = (tSeqList *) list;
tList->length = 0;
}
5.然后求取链表长度
//求链表长度
int SeqList_Length(SeqList *list){
tSeqList *tList = NULL;
if (list == NULL) {
return 0;
}
tList = (tSeqList *) list;
return tList->length;
}
6.然后写元素插入。
思路:
1.判断线性表是否合法
2.判断插入位置是否合法
3.把最后一个元素插入进去之后,要把后面的往后挪动一个单位
4.将新元素插入
5.线性表长度+1
元素插入分为两个阶段,第一个阶段是元素的后移,第二个阶段是给空出的位置进行赋值
元素后移:一般的情况下都好说,要是后移之后超过capacity,要判断length==capacity?或者甚至超过,如果超过立刻return一个提示消息,这是第一种元素后移的bug。第二种元素后移的bug就是,pos的位置,也就是插入位置比length还大,那我们就要进行容错修正,这时就比较自由了,可以重新定义,或者写一个容错修正的算法。
int SeqList_Insert(SeqList *list,SeqListNode *node,int pos){
int ret = 0;
tSeqList *tList = NULL;
if (list == NULL || node == NULL || pos < 0) {
return ret;
}
tList = (tSeqList *) list;
//判断插入元素是否满了
if (tList->length == tList->capacity) {
printf("满了");
}
// 容错修正,pos>length
if (pos > tList->length) {
pos = tList->length;
}
//1.元素后移
int i = 0;
for (i = (*tList).length; i > pos; --i) {
tList->node[i] = (*tList).node[i - 1];
}
//2.插入元素
tList->node[i] = (unsigned int) node;
tList->length++;
}
7.获取元素,获取元素就比插入元素稍微好写一点,要是也要对插入位置pos进行判断。
SeqListNode *list_Get(SeqList *list , int pos){
int i = 0, ret = 0;
tSeqList *tList = NULL;
if (list == NULL || pos < 0) {
ret = -1;
return ret;
}
tList = (tSeqList *) list;
return tList->node[pos];
}
8.删除元素
假设我们删除掉第n个结点,首先我们要把这个结点的位置缓存出来,然后把后面的结点依次赋值过来。
SeqListNode *list_Delete(SeqList *list,int pos){
SeqListNode *ret = 0;
tSeqList *tList = NULL;
if (list == NULL || pos < 0) {
printf("产生了错误");
return NULL;
}
tList = (tSeqList *) list;
ret = (SeqListNode *) tList->node[pos];
for (int i = pos+1; i <tList->length; ++i) {
tList->node[i - 1] = tList->node[i];
}
tList->length--;
return ret;
}
3.优点和缺点
优点:无需为线性表中的逻辑关系增加额外的空间,可以快速的获取表中合法位置的元素
缺点:插入和删除需要移动大量的结点,当线性表长度较大的时候难以确定存储空间的容量