数据结构(二)线性表

提示:本文仅做简单介绍,部分图来自网络,侵删


一、线性表的类型定义

线性表linear_listn 个数据的元素的有限序列,例如:
(1, 2, 3, 4, ……,n)
(A, B, C, D, ……, Z)
(a1, a2, a3, ……, an)
数据元素在不同情况下有不同含义,在复杂的线性表中一个数据元素可以有若干 数据项item 组成,常把数据元素称为 记录record ,含大量记录的线性表称为文件file

以上述线性表为例,a(i-1)ai直接前驱元素,a(i +1)ai直接后继元素。

线性表中元素个数 n (n >= 0) 定义为线性表的长度,当 n = 0 时称为空表,对于非空表中,每个元素的位置都是相对确定的,即有序排列。

抽象数据类型线性表的定义如下:

// 伪代码
ADT list {
	// 数据对象
	D = {a1, a2, a3, ……, an};
	//数据关系
	R1 = {<a(i-1), a(i)>};
	// 基本操作
	InitList(&L) {
		// 构造一个空的线性表L
		
	}
	DestroyList(&L) {
		// 销毁线性表L
		
	}
	ClearList(&L) {
		// 将L重置为空表
		
	}
	ListEmpty(L){
		// 若L为空表,则返回TRUE,否则返回FALSE
		
	}
	ListLength(L) {
		// 返回L表的长度
		
	}
	GetElem(L, i, &e) {
		// 用e返回L中的第i个元素的值
		
	}
	LocateElem(L, e, compare()) {
		// 返回L中第一个e满足compare()的数据元素的位序,e不存在则返回0
		
	}
	PriorElem(L, cur_e, &pre_e) {
		// 用pre_e返回cur_e的直接前驱元素
		
	}
	NextElem(L, cur_e, &next_e) {
		// 用next_e 返回 cur_e的直接后继元素
		
	}
	ListInsert(&L, i, e) {
		// 在L第i个位置前插入元素e,L长度加1

	}
	ListDelete(&L,  i, &e) {
		// 删除L的第i个位置的数据元素,并用e返回删除元素的值,L长度减1

	}
	ListTraverse(L, visit()) {
		// 对L 的每个数据元素调用visit()函数,如果某个元素调用visit()失败,则操作失败

	}
}

union求并集A = AUB 算法时间复杂度O(LA_length * LB_length)

void ListUnion(LA, LB){
	int LA_length = LA.ListLength(LA);
	int LB_length = LB.ListLength(LB);
	for(int i = 0; i < LB_length; i ++) {
		GetElem(LB, i, e);
		if (!LocateElem(LA, e, equal)) {
			ListInsert(LA, ++LA_length, e);
		} 
	} 
	return;
}

merge合并非递减有序线性表 算法时间复杂度O(LA_length + LB_length)

List ListMerge(LA, LB) {
	InitList(LC);
	int i = 0, j = 0, k = 0;
	LA_length = ListLength(LA);
	LB_length = ListLength(LB);
	while (i < LA_length && j < LB_length) {
		GetElem(LA, i, ai);
		GetElem(LB, i, bi);
		if(ai < bi) {
			ListInsert(LC, k, ai);
			k ++;
			i ++;
		} 
		else {
			ListInsert(LC, k, bi);
			k ++;
			j ++;
		}
	}
	while (i < LA_length) {
		GetElem(LA, i, ai);
		ListInsert(LC, k, ai);
	}
	while (j < LB_length) {
		GetElem(LB, i, bi);
		ListInsert(LC, k, bi);
	}
	
	return LC;
}

线性表分为顺序表链表,下面分别介绍

二、线性表的顺序表示和实现

顺序表:使用一组地址连续的存储空间依次存储线性表的数据元素,元素的逻辑位置与物理位置一致。
假设,每个元素占用 SIZE 个存储单元,则可计算表中任意元素位置
LOC(ai) = LOC(a1) + (i - 1) * SIZE

// ----- 线性表的动态分配顺序存储结构 -----
#define	LIST_INIT_SIZE			100
#define	LIST_INCREMENT		10
typedef struct {
	ElemType *elem;
	int length;
	int listSize;		// listSize = sizeof(ElemType)
}SqList;

// ----- 初始化 -----
Status InitList(SqList &l) {
	L.elem = (ElemType *)malloc(LIST_INIT_SIZE * sizeof(ElemType));
	if (! L.elem) {
		exit(OVERFLOW);
	} 
	L.length = 0;
	L.listSIze = LIST_INIT_SIZE ;
	return OK;
}

插入数据元素

Status ListInsert(SqList &L, int i , ElemType e) {
	if(i < 1 && i >L.length + 1) { return ERROR; }
	if(L.length() >= L.listSize) {
		//空间不够
		newBase  = (ElemType *)realloc(L.elem , (L.initSize + LIST_INCREMENT) * sizeof(ElemType));
		if(! newBase) { exit(OVERFLOW); }
		L.elem = newBase;
		L.initSize += LIST_INCREMENT;
	}
	SqList * q = &L.elem[i - 1];
	for(SqList * p = &L.elem[L.length -1]; p >= q; p -- ) { *(p + 1) = *p; }		// 元素后移
	*q = e;
	L.length ++;
	return OK;
}
// O(n / 2)

删除数据元素

Status ListDelete(SqList &L, int i , &e) {
	if(i < 1 && i >L.length + 1) { return ERROR; }
	SqList *p = &L.elem[i - 1];		// p指向e的位置
	e = *p;
	SqList *q = &L.elem[L.length - 1];
	for(++ p; p <= q; ++ p) { *(p - 1) = *p; }			// 元素前移
	L.length --;
	return OK;
}
// O((n - 1) / 2)

查找数据元素

int LocateElem(SqList L, int e, Status (*compare)(ElemType ElemType)) {
	int i = 1;
	p = L.elem;
	while(i <= L.length && !(* compare)(*p ++, e)) { i ++; }
	if(i <= L.length) { return i; }
	else { return 0; }
}

三、线性表的链式表示和实现

链表:使用一组一组任意的存储空间存储线性表的数据元素,这意味着数据元素之间的存储地址可以是不连续的,也可以是连续的。

链表的组成数据元素称为节点node,它包含两个域:存储数据元素的称为数据域,存储直接前驱或直接后驱的称为指针域,通常在第一个节点之前附设一个头节点。单向链表示例:
在这里插入图片描述

typedef struct ListNode{
	ElemType data;
	struct ListNode* next;
}LNode *LinkList;

3.1 线性链表

访问数据元素

Status GetElem(ListNode *head, int i, ElemType &e) {
	ListNode *p = head->next;
	int j  = 0;
	while(j < i && p != NULL) { p  = p->next; j ++; }
	if(j > i || !p) { return ERROR; }
	e = p ->data;
	return OK;
}

插入数据元素

Status ListInsert(ListNode *head,  int i,ElemType e)
{
	// ListNode *dummyNode = new ListNode(-1);
	// dummyNode->next = head;
	ListNode * curr = head;
	int j  = 1;
	while(curr && j < i - 1) {
		curr = curr->next;
		j ++;
	}
	if(!curr || j > i - 1) { return ERROR; }
	ListNode *newNode = new LIstNode(0);
	newNode->data = e;
	newNode->next = curr->next;
	curr->next = newNode;
	return OK; 
}

删除数据元素

Status ListDelete(ListNode *head, int i, ElemType *e) {
	ListNode *curr = head;
	int j = 0;
	while(j < i -1 && curr->next) {
		curr = curr->next;
		j ++;
	}
	if(!curr->next || j > i - 1) { return ERROR; }
	ListNode *delNode = curr->next;
	curr->next = delNode->next;
	e = delNode->data;
	delete delNode;
	return OK;
}

合并非递减单向链表

LIstNode *mergeList(LIstNode *LA, LIstNode *LB) {
	
	LIstNode *LC;
	// LIstNode *pa = LA->next;
	// LIstNode *pb = LB->next;
	LIstNode *pc = LC->next;
	while(LA && LB) {
		if(LA->data <= LB->data) {
			pc ->next = LA;
			LA = LA->next;
			pc = pc->next;
		}
		else {
			pc->next = LB;
			LB = LB->next;
			pc = pc->next;
		}
	}
	pc->next = pa ? pa : pb;
	return PC;
}

3.2 循环链表

循环链表circular linked list 的特点是最后一个节点的指针域指向它的头节点。

循环结束条件curr->next = head

3.3 双向链表

双向链表double linked list

struct DulNode {
	ElemType data;
	DulNode *prior;
	DulNode *next;
	DulNode(ElemType data = 0, DulNode *prior = nullptr, DulNode *next = nullptr);
	DulNode(ElemType data, DulNode* p, DulNode* n) : m_data(data), m_p(p), m_n(n) { }
}

增删改查略

四、总结

线性表中关于顺序表和链表的优缺点:
顺序表(array):访问数据简单,增删数据复杂,适用于查询频繁,很少增删,对存储空间要求不大的情景;
链表(list):数据增删方便,访问耗时,适用于数据量小,频繁增删的情景;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值