数据结构——线性表
基本知识:
数据结构有三要素:逻辑结构、存储结构、运算
1、逻辑结构
2、存储结构
3、运算
** 根据上面的基本知识,我们可以知道,任一个逻辑结构都有两种存储结构和四种基本运算。如:线性结构中的线性表就有顺序存储和链式存储两种存储结构,每一种结构里也对应着有四种基本的运算,增删改查。**
线性表
线性表,是N个具有相同数据类型的数据元素的有限序列。
N为表长,当N=0时,该线性表为空。
例如
播放器的播放列表,是线性表
班级花名册,是线性表
班上人际关系,不是线性表
线性表——顺序存储(顺序表)
用一段地址是连续的存储单元,来存储线性表中的数据元素。
顺序表定义——静态分配空间
#define SIZE 100; //宏定义SIZE为100,也就是在后面的程序中出现了SIZE都代表100,成为以一个常量。
typedef int data_t; //typedef 为一个重命名关键字,将int数据类型,重命名为data_t,除了名字不同以外,功能都相同,好处在于,当我希望把我的所有int类型改为其他类型的时候,只需要更改这里的int
typedef struct list{
data_t data[SIZE]; //用固定大小的数组来存放数据,这就是静态分配,在‘栈区’开辟空间;
int last; //用来存放data[SIZE]数组里最后一个下标的值;
}seqlist; //因为用了typedef,所有后面需要用这个结构体的时候,直接用seqlist就行
顺序表定义——动态分配空间
#define SIZE 100; //宏定义SIZE为100,也就是在后面的程序中出现了SIZE都代表100,成为以一个常量。
typedef int data_t; //typedef 为一个重命名关键字,将int数据类型,重命名为data_t,除了名字不同以外,功能都相同,好处在于,当我希望把我的所有int类型改为其他类型的时候,只需要更改这里的int
typedef struct list{
data_t data[SIZE]; //用固定大小的数组来存放数据;
int last; //用来存放data[SIZE]数组里最后一个下标的值;
}seqlist; //因为用了typedef,所有后面需要用这个结构体的时候,直接用seqlist就行
seqlist *createSeqlist{ //初始化线性顺序表
seqlist *seq=(seqlist *)malloc(sizeof(seqlist)) //使用malloc函数在‘堆区’开辟空间
}
顺序表的基本操作
1、初始化
seqlist *createSeqlist(){ //因为返回值是*seq所指地址,所有需要定义为一个指针函数
seqlist *seq=(seqlist *)malloc(sizeof(seqlist)); //在堆区动态开辟空间
if(NULL==seq) //判断是否成功使用malloc函数在堆区开辟空间,这样写避免将比较运算符==误写为赋值运算符=
return NULL;
memset(seq->data,0,sizeof(seq->data)); //将seq->data所指的大小为sizeof(seq->data)空间清0
seq->last=-1; //将seq->last赋值为-1,就可以代表数组中最后一个数值的下标,seq->last+1则为长度
return seq;
2、判断顺序表是否为空
int seqlist_is_empty(seqlist *seq){
if( seq == NULL) //同上
return -1;
return ((seq->last == -1)?1:0); //使用三木运算符,当下标为-1时,也就是顺序表中没有值时,则返回1;若不唯一则证明顺序表不为空,则返回0
}
3、判断顺序表是否满
//和判断是否为空,异曲同工
int seqlist_is_full(){
if(seq == NULL)
return -1;
return ( (seq->last+1 == SIZE )?1:0 );
}
4、求顺序表长度
int seqlist_length(seqlist *seq){
if(seq == NULL)
return -1;
return seq->last+1; //Seq->last代表顺序表的最后一个值的下标,最后一个值的下标+1则为长度
}
5、按位置插入内容
int insertSeqlistByPos(seqlist *seq,int pos,data_t data){
if(seq == NULL)
return -1;
if(seqlist_is_full(seq) == 1) //判断顺序表是否已经满了,如果满了则不能继续插入数据
return -1;
int len = seqlist_length(seq); //定义一个变量接收顺序表的长度,判断所插入位置是否在可插入范围内
if(pos < 0 || pos > len)
return -1;
int i;
for(i = seq->last ; i>= pos; i--) //如果在可插入范围内,则开始插入
{
seq->data[i+1] = seq->data[i]; //顺序表中插入一个值,需要先将所查位置以及后面所有值一个一个往后挪出一个位置来,让给插入的值
}
seq->data[pos] = data;
seq->last++; //因为插入了一个值,整体往后挪了一位,所以数组最后一个指针也应该往后移一位
}
6、按位置删除内容
int deleteSeqlistByPos(seqlist *seq, int pos)
{
if(seq == NULL)
return -1;
if(seqlist_is_empty(seq) == 1)
return -1;
int len = seqlist_length(seq);
if(pos < 0 || pos > seq->last)
return -1;
int i;
for(i=pos; i<seq->last; i++)
{
seq->data[i] = seq->data[i+1];
}
seq->last--;
}
7、按位置查找内容
data_t findSeqlistByPos(seqlist *seq, int pos)
{
if(NULL == seq)
return -1;
if( seqlist_is_empty(seq) == 1)
return -1;
int len = seqlist_length(seq);
if(pos < 0 || pos > seq->last)
return -1;
return seq->data[pos];
}
8、按位置更改内容
int changeSeqlistByPos(seqlist *seq, int pos, data_t data)
{
if(NULL == seq)
return -1;
if( seqlist_is_empty(seq) == 1)
return -1;
int len = seqlist_length(seq);
if(pos < 0 || pos > seq->last)
return -1;
seq->data[pos] = data;
}
9、按内容查找下标
int findSeqlistByData(seqlist *seq, data_t data)
{
if(NULL == seq)
return -1;
if(seqlist_is_empty(seq) == 1)
return -1;
int i;
for(i=0 ; i<= seq->last; i++)
{
if( seq->data[i] == data)
{
return i;
}
}
}
10、按内容删除
int deleteSeqlistByData(seqlist *seq, data_t data)
{
int pos = findSeqlistByData(seq, data);
deleteSeqlistByPos(seq, pos);
}
11、按内容修改
int changeSeqlistByData(seqlist *seq, data_t old_data, data_t new_data)
{
int pos = findSeqlistByData(seq, old_data);
changeSeqlistByPos(seq, pos, new_data);
}
12、打印顺序表
void displaySeqlist(seqlist *seq)
{
if(NULL == seq)
return;
if(seqlist_is_empty(seq) == 1)
return;
int i;
for(i=0; i<=seq->last; i++)
{
printf("%d ", seq->data[i]);
}
puts("");
}
13、清空
void clearSeqlist(seqlist *seq)
{
if(NULL == seq)
return;
seq->last = -1;
}
14、销毁
void destroySeqlist(seqlist **seq)
{
free(*seq);
*seq = NULL;
}
15、测试主函数
int main(int argc, const char *argv[])
{
//静态分配, 定义一个结构体变量即可
seqlist seq = { {0}, -1 };
#if 0
seqlist *seq = createSeqlist();
if(NULL == seq)
{
printf("malloc falied!\n");
return -1;
}
#endif
int n = seqlist_is_empty(&seq);
printf("line:%d n:%d\n", __LINE__, n);
int m = seqlist_is_full(&seq);
printf("line:%d m:%d\n", __LINE__, m);
int len = seqlist_length(&seq);
printf("line:%d len:%d\n", __LINE__, len);
//按位置来插入数据元素
int i = 0;
while(i<10)
{
insertSeqlistByPos(&seq, i, i+1);
i++;
}
len = seqlist_length(&seq);
printf("line:%d len:%d\n", __LINE__, len);
displaySeqlist(&seq);
//按位置来删除数据元素
deleteSeqlistByPos(&seq, 2);
displaySeqlist(&seq);
len = seqlist_length(&seq);
printf("line:%d len:%d\n", __LINE__, len);
//按位置来查找数据元素
data_t data = findSeqlistByPos(&seq, 5);
printf("data = %d\n", data);
//按位置来修改数据元素
changeSeqlistByPos(&seq, 1, 250);
displaySeqlist(&seq);
//按值来查找数据元素
int pos = findSeqlistByData(&seq, 250);
printf("line:%d pos = %d\n", __LINE__, pos);
//按值来删除数据元素
deleteSeqlistByData(&seq, 7);
displaySeqlist(&seq);
//按值来修改数据元素
changeSeqlistByData(&seq, 10, 100);
displaySeqlist(&seq);
//清空顺序表
clearSeqlist(&seq);
displaySeqlist(&seq);
len = seqlist_length(&seq);
printf("line:%d len:%d\n", __LINE__, len);
//销毁顺序表
destroySeqlist(&seq);
printf("%p\n", seq);
return 0;
}
线性表——链式存储(链表)
1、单链表
插入
删除
双链表
插入
删除
单向循环链表
插入、删除等同单链表一样,只是判断这个链表是否遍历完的条件变成了是否等于head
双向循环链表
插入、删除等同双向链表,只是判定是否遍历完成的条件从是否等于NULL变为了是否等于head
顺序表VS链表
顺序表
1、【支持随机访问】:因为顺序表的结构同数组的结构是一样的,可以利用下标来任意访问数据
2、【存储密度较高】:因为数组每个存储单元的密度为1,每个单元都是用来存储数据的,所以不存在浪费空间
3、【是一大片的连续的存储空间,分配空间和改变容量都不方便】
4、【插入和删除元素是,其后元素都要往后或往前移动,不方便】
链表
1、【不支持随机访问】:因为链表是采用链式存储结构,只能通过遍历,从头节点开始找,直到找到相应节点。
2、【存储密度较低】:因为链表的每个存储单元都需要存储非数据项,如还需分配一部分空间出来存储next/prior指针,所有会造成一部分空间的浪费,存储密度较低
3、【离散的小空间分配方便,改变容量也方便】
4、【插入和删除节点时只需改变节点的连接即可】
综合上述所言,顺序表和单链表各有各的优缺点,使用哪一种会好一些要结合具体的问题而言,不能一概而论。
比如:
在查询操作使用的比较频繁时,使用顺序表会好一些;
在插入、删除操作使用的比较频繁时,使用单链表会好一些。