线性表

数据结构的框架如图:
逻辑

线性表、栈和队列、串都是线性结构。
特点是:在数据元素的非空有限集中,

  1. 存在唯一一个被称为“第一个”的数据元素
  2. 存在唯一一个被称为“最后一个”的数据元素
  3. 除第一个外,集合中每个数据元素均只有一个前驱
  4. 除最后一个外,集合中每一个数据元素均只有一个后继

注:非空、有限
线性表由数据元素组成,数据元素是由数据项构成

线性表
顺序表
链表

1、顺序表

用一组地址连续的存储单元依次存放数据元素
顺序表
元素物理地址 = 起始地址 + 存储单元大小*第i个元素
L o c ( e i ) = L o c ( e 0 ) + c ∗ i Loc(e_i) = Loc(e_0) + c*i Loc(ei)=Loc(e0)+ci

顺序表的操作

  • InitList(Sqlist &L) 构造空的线性表
  • CreateList(Sqlist &L, int L_count) 向表中填入内容
  • PrintList(Sqlist L) 打印顺序表
  • InsertList(Sqlist &L, int pos, int elem) 向顺序表第pos个位置插入元素elem
  • DeleteList(Sqlist &L, int pos) 删除顺序表中位置pos的元素
  • SortList(Sqlist L, int elem) 查找顺序表中的元素elem
1.1 顺序表的实现
// 构造空的线性表 
int InitList(Sqlist &L){
	L.elem = (int *)malloc(LIST_INIT_SIZE * sizeof(int)); // 申请表空间(大小=表长*字符大小)
	if(! L.elem)
		return 0;
	L.length = 0;
	L.listsize = LIST_INIT_SIZE;
}

// 向表中填入内容 
void CreateList(Sqlist &L, int L_count){
	int i;
	for(i=0; i<L_count; i++){
		printf("请输入元素:");
		scanf("%d", &L.elem[i]);
		L.length ++;
	}
}

// 打印顺序表 
void PrintList(Sqlist L){
	int i;
	for(i=0; i<L.length; i++){
		printf("位置%d", i);
		printf("元素%d\n", L.elem[i]);
	}
	printf("\n");
}
1.2 插入操作
// 向顺序表L第pos个位置插入元素elem
int InsertList(Sqlist &L, int pos, int elem){
	int *newbase;
	int *p, *q;
	if(pos<0 || pos>L.length)
		return 0;
	if(L.length >= L.listsize){
		newbase = (int *)realloc(L.elem, (L.listsize + LISTINCREMENT)*sizeof(int)); // 超过预设空间,重新申请空间
		if(! newbase)
			return 0;
		L.elem = newbase;
		L.listsize += LISTINCREMENT;
	}

	q = &(L.elem[pos]);
	for(p=&(L.elem[L.length-1]); p>=q; --p)
		*(p+1) = *p;
	*q = elem;
	++ L.length; 
} 

注:其中的realloc函数,参考这篇博文c语言中realloc()函数解析
*如果将分配的内存减少,realloc仅仅是改变索引的信息。
如果是将分配的内存扩大,则有以下情况:
1)如果当前内存段后面有需要的内存空间,则直接扩展这段内存空间,realloc()将返回原指针。
2)如果当前内存段后面的空闲字节不够,那么就使用堆中的第一个能够满足这一要求的内存块,将目前的数据复制到新的位置,并将原来的数据块释放掉,返回新的内存块位置。
3)如果申请失败,将返回NULL,此时,原来的指针仍然有效。
也就是说成功操作后,所建为新指针,旧指针被抛弃无法再操作。

1.3 删除操作
// 删除顺序表中位置pos的元素 
int DeleteList(Sqlist &L, int pos){
	int *p, *q;
	int i;
	if(pos<0 || pos>L.length)
		return 0;
	p = &L.elem[pos]; // 被删除元素位置 
	q = L.elem + L.length - 1; // 表尾元素 
	for(p; p<q; p++){
		*p = *(p+1);
	}
	--L.length;
}
1.4 查找操作
// 查找列表中的元素 
int SortList(Sqlist L, int elem){
	int i;
	for(i=0; i<L.length; i++){
		if(elem == L.elem[i]){
			return i; 
		}
	}
	return -1;
} 

在顺序查找这里有一个监督元技术,通过预设一个监督元,减少判断条件,获取效率
在这里插入图片描述

顺序表这里比较简单,主要注意它在插入和删除时会有大量的元素移动。不过查找上很快捷,可以参考C语言的数组操作。

2、普通链表

链表的种类如下
链表家族
首先介绍一下普通链表的操作

  • CreateList(LinkList &L, int L_count)建立单链表
  • PrintList(LinkList L)打印链表
  • InsertList(LinkList &L, int pos, int elem)向链表第pos个位置插入元素elem
  • DeleteList(LinkList &L, int pos)删除链表中位置pos的元素
  • SortList(LinkList L, int elem)查找列表中的元素

单链表结点定义:
typedef struct LNode{
ElemType data; //数据域
struct LNdode *next; //指针域
}

2.1 链表的实现

在建立链表时注意两点

  • 带不带头结点
  • 头插法or尾插法

头结点就是设置一个头部空结点,这里面不会存入数据值,只有NULL或者指向下一个结点。
优点在于:1、无论链表是否为空,头指针都指向头结点。
2、在链表的第一个位置上的操作和在表的其他位置上的操作一致。

在链表中设置头结点有什么好处?

  1. 便于首元结点的处理
  2. 便于空表和非空表的统一处理
// 使用不带头结点的尾插法建立单链表L
void CreateList(LinkList &L, int L_count){ //L_count为新建链表长
	int i;
	LNode *p, *last;
	L = last = (LinkList)malloc(sizeof(LNode));
	L = NULL; //此链表无头结点,空链表头指针L直接为空
	if(L_count == 0)
		return;
	for(i=0; i<L_count; i++){
		p = (LinkList)malloc(sizeof(LNode)); // 生成新结点 
		printf("请输入元素:");
		scanf("%d", &p->data);
		p->next = NULL;
		if(L == NULL){ 
			L = p;
			last = p;     
		}
		else{
			last->next = p;
			last = p;                                                                                                                                                  
		}
	}
}
// 使用带头结点的头插法建立单链表L
void CreateList(LinkList &L, int L_count){ //L_count为新建链表长
	int i;
	LNode *p;
	L = (LinkList)malloc(sizeof(LNode)); //建立头结点L
	L->next = NULL;
	if(L_count == 0)
		return;
	for(i=0; i<L_count; i++){
		p = (LinkList)malloc(sizeof(LNode)); // 生成新结点 
		printf("请输入元素:");
		scanf("%d", &p->data);
		p->next = L->next;
		L->next = p;
	}
}

操作过程可以见下图
在这里插入图片描述
所以头插和尾插区别不大,尾插需要标记一个尾结点标志last
使用区别上,主要是为了得到链表数据次序不同。

// 打印链表               
void PrintList(LinkList L){
	int i=0;
	if(L == NULL){
		printf("链表为空\n"); 
	}
	else{
		while(L->next != NULL){
			printf("位置%d,元素%d\n", i, L->data);
			L = L->next;
			i ++;
		}
		printf("位置%d,元素%d\n", i, L->data);
	}
	printf("\n");
}
2.2 插入操作
// 向链表第pos个位置插入元素elem
int InsertList(LinkList &L, int pos, int elem){
	int i=0;
	LNode *p;
	p = (LinkList)malloc(sizeof(LNode));
	p->data = elem;
	if(pos == 0){
		// 插入位置为链表首 
		p->next = L;
		L = p;
	}
	else{
		while(i<pos-1){
			i ++;
			L ++;
		}
		p->next = L->next;
		L->next = p; 
	}
}     
2.3 删除操作
// 删除链表中位置pos的元素 
int DeleteList(LinkList &L, int pos){
	int i=0;
	LNode *p, *flag_node;
	
	if(pos == 0){
		// 被删除的是链表首 
		printf("被删除的元素是%d\n", L->data); 
		L = L->next;
	}     
	else{
		flag_node = p = (LinkList)malloc(sizeof(LNode));
		flag_node = L;
		while(i<pos-1){
			i ++;
			flag_node = flag_node->next;
		}
		p = flag_node->next;
		printf("被删除的元素是%d\n", p->data);
		flag_node->next = p->next;
	}
}
2.4 查找操作
// 查找列表中的元素 
int SortList(LinkList L, int elem){
	int i=0;
	while(L != NULL){
		if(L->data == elem){
			return i;
		}
		L = L->next;
		i ++;
	}
	return -1;
} 

3、特殊链表

3.1 双链表

双链表在单链表的结点中加入了一个前驱指针
单链表对于其前驱结点的访问需要O(n),增加一个前驱指针后,插入、删除结点的时间为O(1)

双链表的结点定义
typedef struct DNode{
ElemType data;
struct DNode *prior,*next; //前驱和后继指针
}DNode, *DLinkList;

下图为双链表插入操作
在这里插入图片描述
双链表的结点删除操作如下:
先进行①②,然后③free( p); //释放p结点空间
在这里插入图片描述

3.2 循环链表
  1. 循环单链表的最后一个结点的指针不是NULL,而是指向头结点。
  2. 循环单链表在任意位置插入、删除结点操作一样,无需判断是否为尾结点
  3. 循环双链表为空表时,头结点的prior和next指针域都是L
3.3 静态链表

静态链表采用数组形式来描述链表,一行数据分两块,一块放置数据,一块放置下一个数的数组下标。
在这里插入图片描述
静态链表常用于不支持指针的语言中

4、顺序表与链表优势与劣势

  1. 顺序表的缺点:在做插入或者删除操作时需要移动大量元素;静态存储,数据元素的个数不能自由扩充
  2. 顺序表最主要特点:随机存取,即通过首地址和元素序号可在时间O(1)内找到指定元素
  3. 链表的优点:不需要预先确定链表的大小,是一种动态结构(需要多少加多少)
  4. 链表的缺点:在进行查找操作和求链表长度操作时麻烦,失去了顺序表可以随机存取的优点

小狼的相关博文:

  1. 算法(Algorithm)与数据结构(Data Structure)——索引

  2. 数据结构和算法系列代码测试代码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值