线性表详解

目录

 

1.线性表的定义和特点

2.案例

2.1一元多项式的计算

可以通过下面这个题目简单练习一下

2.2稀疏多项式的计算

2.3图书信息管理系统

3.线性表的类型定义

4.线性表的顺序表示和实现

4.1线性表的顺序储存表示

4.2顺序表中基本操作的实现

5.线性表的链式表现和实现

5.1单链表的定义和实现

5.2单链表的基本操作实现

5.3循环链表

5.4双向链表

6.顺序表和链表的比较

6.1空间性能

6.2时间性能

7.线性表的应用

7.1线性表的合并

7.2有序表的合并

8.小结


1.线性表的定义和特点

定义:由n(n≥0)个数据特性相同的元素构成的有限序列称为线性表。

线性表中的元素个数n定义为线性表的长度。n=0时为空链。

特点:

(1)存在唯一的一个被称为“第一个”的数据元素

(2)存在唯一的一个被称为“最后一个”的数据元素

(3)除第一个以外,结构中的每个数据元素均只有一个前驱;

(4)除最后一个以外,结构中的每个数据元素均只有一个后继;

2.案例

2.1一元多项式的计算

用两个线性表来储存系数,这样就转化为两个链表对应元素相操作的问题了

可以通过下面这个题目简单练习一下

剑指 Offer II 025. 链表中的两数相加 - 力扣(Leetcode)

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        //方法: 把所有数字压入栈中,再依次取出相加
        stack<int> s1, s2;
        while (l1) {
            //1.把l1中所有数字压入栈s1中
            s1.push(l1 -> val);
            l1 = l1 -> next;
        }
        while (l2) {
            //2.把把l2中所有数字压入栈s2中
            s2.push(l2 -> val);
            l2 = l2 -> next;
        }
        int carry = 0;

        ListNode* ans = nullptr;//用于储存最后返回的值
        
        while (!s1.empty() or !s2.empty() or carry != 0) {
            int a = s1.empty() ? 0 : s1.top();
            int b = s2.empty() ? 0 : s2.top();
            if (!s1.empty()) s1.pop();
            if (!s2.empty()) s2.pop();
            int cur = a + b + carry;
            carry = cur / 10;
            cur %= 10;
            auto curnode = new ListNode(cur);
            curnode -> next = ans;
            ans = curnode;
        }
        return ans;
    }
};

2.2稀疏多项式的计算

稀疏多项式的次幂跳度往往很大,这时如果再按照每个次幂来记录对应系数会有大量的0,比较浪费空间。这时我们采用(系数,指数)来记录多项式。

用一个长度为m且每个元素由两个数据线(系数项和指数项)的线性表。和多项式的计算其实差不多,不过原来简单多项式的线性表元素是单个数,稀疏多项式用的是(m,n)这样的数对。

具体的代码在后面介绍(c++中有个很好的结构叫做(pair)键值对,在树的对应章节会介绍)

2.3图书信息管理系统

出版社有一些图书数据保存在一个文本文件 book . txt 中,为简单起见,在此假设每种图书只包括三部分信息: ISBN (书号)、书名和价格,文件中的部分数据如图2.1所示。现要求实现一个图书信息管理系统,包括以下6个具体功能。
(1)查找:根据指定的 ISBN 或书名查找相应图书的有关信息,并返回该图书在表中的位置序号。(2)插人:插入一种新的图书信息。
(3)删除:删除一种图书信息。
(4)修改:根据指定的 ISBN ,修改该图书的价格。
(5)排序:将图书按照价格由低到高进行排序。
(6)计数:统计图书表中的图书数量。

要实现上述功能,与以上案例中的多项式一样,我们首先根据图书表的特点将其抽象成一个线性表,每本图书作为线性表中一个元素,然后可以采用适当的存储结构来表示该线性表,在此基础上设计完成有关的功能算法。具体采取哪种存储结构,可以根据两种不同存储结构的优缺点,视实际情况而定。

具体代码实现会在后面单独的一篇博文给出

3.线性表的类型定义

说明

(1)出翔数据类型进士一个模型的定义,不涉及模型的具体实现(不是某种语言的代码实现),下面描述的所涉及参数不考虑具体数据类型;

(2)下面抽象数据类型中给出的操作只是基本的操作。

ADT List{
数据对象:D={ai|ai∈ElemSet,i=1,2,...n,n≥0}
数据操作关系:R={<ai-1,ai>,ai∈D,i=2,...,n}
基本操作;
InitList(&L)
操作结果:构造一个空的线性表L(初始化)
DestoryList(&L)
初始条件:线性表L已经存在
操作结果:销毁线性表L
ClearList(&L)
初始条件:线性表L已经存在
操作结果:将线性表L重置为空表
ListEmpty(L)
初始条件:线性表L已经存在
操作结果:若线性表L为空表,返回true,若不是,返回false
ListLength(L)
初始条件:线性表L已经存在
操作结果:返回线性表L中数据元素个数
GetElem(L,i,&e)
初始条件:线性表L已经存在,且1≤i≤ListLength(L)
操作结果:用e返回L中第i个数据元素的值
LocateElem(L,e)
初始条件:线性表L已经存在
操作结果:用e返回L中第1个和e相同的元素在L中的位置。若这样的值不存在,则返回值为空值
ProirElem(L,cur_e,&pre_e)
初始条件:线性表L已经存在
操作结果:若cur_e是L的数据元素,且不是第一个,则用pre_e返回其前驱,否则操作失败,pre_e无定义
NextElem(L,cur_e,&next_e)
初始条件:线性表L已经存在
操作结果:若cur_e是L的数据元素,且不是最后一个,则用next_e返回其前驱,否则操作失败,next_e无定义
ListInsert(&L,i,e)
初始条件:线性表L已经存在且1≤i≤ListLength(L)
操作结果:在L中的第i个位置之前按插入新的元素e,L的长度+1.
ListDelete(&L,i)
初始条件:线性表L已经存在且非空,且1≤i≤ListLength(L)
操作结果:删除L中的第i个位置的元素,L的长度-1.
TraverseList(L)
初始条件:线性表L已经存在
操作结果:对线性表L进行遍历,在遍历过程中对L的每个元素访问一次。

4.线性表的顺序表示和实现

4.1线性表的顺序储存表示

定义:用一组地址连续的存储单元一次存储线性表的数据元素,这种表示方法也称作顺序储存结构或顺序映像。通常,称这种存储结构的线性表为顺序表

特点:逻辑上相邻的元素,物理位置上也相邻。

在c语言中可以用动态分配的一维数组来表示线性表,描述如下

#define N 200
typedef int SLDataType;
struct SeqList
{
    SLDataType *elem;  //存储空间的基地址
	int length;        //当前长度
}SqList;               //顺序表的结构类型为SqList

4.2顺序表中基本操作的实现

//顺序表中基本操作的实现
//初始化
bool InitList(SeqList& L)
{
	L.elem = new SLDataType[N];
	if (!L.elem) exit(-1);
	L.length = 0;
	return true;
}
//取值
bool GetElem(SeqList L, int i, SLDataType& e) {
	if (i<1 || i>L.length) return false;

	e = L.elem[i - 1];
	return true;
}
//查找
int LocateElem(SeqList L,SLDataType e)
{
	for (int i = 0;i < L.length;i++)
	{
		if (L.elem[i] == e) 
			return i + 1;
	}
	return 0;
}
//插入
bool ListInsert(SeqList& L, int i, SLDataType e)
{
	if ((i < 1) || (i > L.length + 1))return false;
	if (L.length == N) return false;
	
	for (int j = L.length - 1;j >= i - 1;j++)
	{
		L.elem[j + 1] = L.elem[j];
		L.elem[i - 1] = e;
		L.length++;

		return true;
	}
}

//删除
bool ListDelete(SeqList& L, int i)
{
	if ((i < 1) || (i > L.length)) return false;
	for (int j = i;j <= L.length - 1;j++)
	{
		L.elem[j - 1] = L.elem[j];
		L.length--;
	}
	return true;
}

5.线性表的链式表现和实现

静态顺序表存在一个问题:N太小,可能不够用,N太大,可能浪费空间,所以很多时候链式线性表更为常用。

5.1单链表的定义和实现

特点:用一组任意的春初单元存储线性表的数据元素

定义:

//1.定义
typedef int SLDataType;

typedef struct LNode {
	SLDataType data;	/*数据域*/
	struct LNode* next; /*指针域*/
}LNode,*LinkList;

5.2单链表的基本操作实现

//2.初始化
bool InitList(LinkList& L)
{
	L = new LNode;
	L->next = NULL;
	return true;
}

//3.取值
bool GetElem(LinkList L,int i, SLDataType& e)
{
	LNode *p = L->next;
	int j = 1;
	while (p && j < i)
	{
		p = p->next;
		j++;
	}
	if (!p || j > i) 
		return false;

	e = p->data;
	return true;
}

//4.查找
LNode* LocateElem(LinkList L, SLDataType e)
{
	LNode* p = L->next;
	while (p && p->data != e)
	{
		p = p->next;
	}
	return p;
}


//5.插入
bool ListInsert(LinkList L,int i,SLDataType e)
{
	LNode* p = L;int j = 0;
	while (p && (j < i - 1))
	{
		p = p->next;
		j++;
	}
	if (!p || j > i - 1)
		return false;

	LNode* s = new LNode;
	s->data = e;
	s->next = p->next;
	p->next = s;

	return true;
}

//6.删除
bool ListDelete(LinkList& L, int i)
{
	LNode* p = L;
	int j = 0;

	while ((p->next) && j < i - 1)
	{
		p = p->next;j++;
	}
	if (!p || j > i - 1)
		return false;
}


//前插法创建单链表
void CreateList_H(LinkList& L, int n)
{
	L = new LNode;
	L->next = NULL;
	
	for (int i = 0;i < n;i++)
	{
		LNode* p  = new LNode;
		cin >> p->data;
		p->next = L->next;
		
		L->next = p;

	}
}

//后插法创建新链表
void CreateList_R(LinkList& L, int n)
{
	LNode* L = new LNode;
	LinkList r = L;
	for (int i = 0;i < n;++i)
	{
		LNode *p = new LNode;
		cin >> p->data;

		p->next = NULL;r->next = p;
		r = p;
	}
}

5.3循环链表

特点:表中最后一个节点的指针域指向头结点

操作与单链表基本类似。

区别:遍历链表的判别条件不同,为 p!=NULL或者p->next!=NULL;

5.4双向链表

✍双向链表几乎和单向列表一样。唯一的不同是每一个节点都要两个指针。与单向列表相同的是他的继承点都指向下一个节点(如果存在)。但是每个节点有一个额外的指针指向前面的一个节点(如果存在)。

使用这个额外的指针能够让我们从链表的末尾开始向前迭代,但是这样会使用两倍的内存。但是他的好处是我们会很容易的搜索,插入或者 删除比较靠后的节点,其时间复杂度是O(1)。如果用单向列表做那么时间复杂度为O(n)。

 不带头结点的单链表是链表的最简单的一种结构,最复杂的一种结构是带有头结点的双向循环链表。

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

typedef int LTDataType;
typedef struct ListNode
{
	struct ListNode* next;		//指向直接前驱
	struct ListNode* prev;		//指向直接后继
	LTDataType data;			//数据域
}LTNode;


LTNode* BuyListNode(LTDataType x)
{
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	if (node == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	node->data = x;
	node->next = NULL;
	node->prev = NULL;
	return node;
}

//void ListInit(LTNode** pphead)
//{
//	*pphead = BuyListNode(-1);
//	(*pphead)->next = *pphead;
//	(*pphead)->prev = *pphead;
//}

LTNode* ListInit()
{
	LTNode* phead = BuyListNode(-1);
	phead->next = phead;
	phead->prev = phead;

	return phead;
}

void ListPrint(LTNode* phead)
{
	assert(phead);

	LTNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

void ListPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);

	/*LTNode* newnode = BuyListNode(x);
	LTNode* tail = phead->prev;
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = phead;
	phead->prev = newnode;*/

	ListInsert(phead, x);

}

void ListPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);

	//LTNode* newnode = BuyListNode(x);
	//LTNode* next = phead->next;

	 phead newnode next
	//phead->next = newnode;
	//newnode->prev = phead;
	//newnode->next = next;
	//next->prev = newnode;

	ListInsert(phead->next, x);
}

//void ListPushFront(LTNode* phead, LTDataType x)
//{
//	assert(phead);
//
//	LTNode* newnode = BuyListNode(x);
//
//	phead->next->prev = newnode;
//	newnode->next = phead->next;
//
//	phead->next = newnode;
//	newnode->prev = phead;
//}

void ListPopBack(LTNode* phead)
{
	assert(phead);

	assert(phead->next != phead);
	//assert(!ListEmpty(phead));

	/*LTNode* tail = phead->prev;
	LTNode* tailPrev = tail->prev;

	free(tail);

	tailPrev->next = phead;
	phead->prev = tailPrev;*/

	ListErase(phead->prev);
}

void ListPopFront(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);

	ListErase(phead->next);
}

bool ListEmpty(LTNode* phead)
{
	assert(phead);

	return phead->next == phead;
}

// 在pos位置之前插入x
void ListInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* prev = pos->prev;
	LTNode* newnode = BuyListNode(x);

	// prve newnode pos
	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;
}

//  删除pos位置的节点
void ListErase(LTNode* pos)
{
	assert(pos);
	LTNode* prev = pos->prev;
	LTNode* next = pos->next;

	prev->next = next;
	next->prev = prev;
	free(pos);
}

6.顺序表和链表的比较

6.1空间性能

(1)储存空间的比较

顺序表的存储空间必须预先分配,元素个数扩充受到一定限制,易造成存储空间的浪费或者空间一处现象。链表则不需要预先分配空间,对空间的利用更充分。

(2)存储密度的大小

存储密度=数据元素本身所占存储量/节点结构占用的存储量。如果不考虑

6.2时间性能

(1)存取性能的比较

顺序表---O(1)         链表---O(n)

(2)删除和插入

顺序表---O(n)     链表---O(1) 

7.线性表的应用

7.1线性表的合并

可以参考这个题目的练习

1669. 合并两个链表 - 力扣(Leetcode)

class Solution {
public:
    ListNode* mergeInBetween(ListNode* list1, int a, int b, ListNode* list2) {
        ListNode* preA = list1;
        for (int i = 0; i < a - 1; i++) {
            preA = preA->next;
        }
        ListNode* preB = preA;
        for (int i = 0; i < b - a + 2; i++) {
            preB = preB->next;
        }
        preA->next = list2;
        while (list2->next != nullptr) {
            list2 = list2->next;
        }
        list2->next = preB;
        return list1;
    }
};



 

7.2有序表的合并

21. 合并两个有序链表 - 力扣(Leetcode)

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        if (l1 == NULL) {
            return l2;
        }
        if (l2 == NULL) {
            return l1;
        }
        if (l1->val <= l2->val) {
            l1->next = mergeTwoLists(l1->next, l2);
            return l1;
        }
        l2->next = mergeTwoLists(l1, l2->next);
        return l2;
    }
};

8.小结

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值