线性表List:具有相同数据类型的n(n>=0)个数的有限序列

一般表示形式:L = (a1,a2,... ,ai,... ,an)
a1是第一个数据元素,称为表头元素,an是最后一个数据元素,称为表尾元素。除第一个元素外,每一个元素有且只有一个前驱。除最后一个元素外,每一个元素有且只有一个后驱。

线性表是一种逻辑结构,表示元素之间一对一的相邻关系。顺序表和链表是指储存结构。


顺序表:线性表的顺序存储又称顺序表。用一组地址连续的存储单元依次存储线性表中的数据元素的线性表。

  • 静态顺序表:使用定长数组存储元素
    #include<stddef.h>//size_t是无符号整数类型,通常用于表示对象的大小或数组的索引等。它的定义在stddef.h头文件中。
    #include<stdio.h>
    
    typedef struct SeqList{
    	int arr[100];//静态分配,数组长度固定
    	size_t size;//当前顺序表的长度
    }SeqList;//给结构体起别名,后续使用时可以直接写 SeqList,不用写 struct SeqList
  • 动态顺序表:使用动态开辟的数组存储

    typedef struct SeqList{
    	int* a;//顺序表的数组指针,指向存储元素的内存空间
    	int size;//当前顺序表中元素的数量
    	int capacity;//当前分配的内存空间可以容纳的元素数量
    }SeqList;

C语言malloc,free函数;C++new,delete关键字。malloc返回一篇连续的存储空间的指针

(int*)malloc(sizeof(int)*10)

静态顺序表适用于知道存多少数据的场景。所以一般使用动态顺序表。

//初始化
void SLInit(SL* psl)
{
	assert(psl);
	psl->a = NULL;
	psl->size = 0;
	psl->capacity = 0;
}
 
//销毁
void SLDestory(SL* psl)
{
	assert(psl);
	free(psl->a);
	psl->a = NULL;
	psl->size = psl->capacity = 0;
}
 
//输出操作
void SLPrint(SL* psl)
{
	assert(psl);
 
	for (int i = 0; i < psl->size; i++)
	{
		printf("%d", psl->a[i]);
	}
	printf("\n");
}
 
//扩容
void SLCheckCapacity(SL* psl)
{
	assert(psl);
 
	if (psl->size == psl->capacity)
	{
		int newCapacity = psl->capacity == 0 ? 4 : psl->capacity * 2;
		SLDataType* tmp = (SLDataType*)realloc(psl->a,(sizeof(SLDataType) * newCapacity));
		if (tmp == NULL)
		{
			perror("sealist newcapacity realloc fail");
			exit(-1);
		}
		psl->a = tmp;
		psl->capacity = newCapacity;
	}
}
 
//尾插
void SLPushBack(SL* psl, SLDataType x)
{
	assert(psl);
	SLCheckCapacity(psl);
 
	psl->a[psl->size] = x;
	psl->size++;
}
 
//头插
void SLPushFront(SL* psl, SLDataType x)
{
	assert(psl);
	SLCheckCapacity(psl);
	
	for (int end = psl->size - 1; end >= 0; end--)
	{
		psl->a[end + 1] = psl->a[end];
	}
	psl->size++;
	psl->capacity++;
}
 
//尾删
void SLPopBack(SL* psl)
{
	assert(psl);
	assert(psl->size > 0);
	psl->size--;
}
 
//头删
void SLPopFront(SL* psl)
{
	assert(psl);
	assert(psl->size > 0);
 
	int begin = 1;
	while (begin < psl->size)
	{
		psl->a[begin - 1] = psl->a[begin];
		begin++;
	}
	psl->size--;
}
 
//pos是下标,插入
void SLInsert(SL* psl, int pos, SLDataType x)
{
	assert(psl);
	assert(pos >= 0 && pos < psl->size);
 
	SLCheckCapacity(psl);
 
	int end = psl->size - 1;
	while (end >= pos)
	{
		psl->a[end + 1] = psl->a[end];
		end--;
	}
	psl->a[pos] = x;
	psl->size++;
}
 
//删除
void SLErase(SL* psl, int pos)
{
	assert(psl);
	assert(pos >= 0 && pos < psl->size);
 
	int begin = pos + 1;
	while (begin < psl->size)
	{
		psl->a[begin - 1] = psl->a[begin];
		++begin;
	}
	psl->size--;
}
 
//按值查找
int SLFind(SL* psl, SLDataType x)
{
	assert(psl);
 
	for (int i = 0; i < psl->size; i++)
	{
		if (psl->a[i] == x)
		{
			return i;
		}
	}
}
 
//表长
int SLSize(SL* psl)
{
	assert(psl);
 
	return psl->size;
}
 
//判空
bool SLEmpty(SL* psl)
{
	if (psl == NULL)
		return true;
	else
		return false;
}

顺序表的问题及思考

中间/头部的插入删除,时间复杂度为O(N)
增容需要申请新空间,拷贝数据,释放就空间。有不小的消耗。
增容一般呈两倍增长,可能造成空间浪费。

解决以上问题引出链表结构,也是线性表的一种。


链表:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

  • 单链表:线性表的链式存储又称单链表,它是指通过一组任意的存储单元来存储线性表中的数据元素。
    typedef struct SlistNode{
    	int data;
    	struct SlistNode* next;
    }SlistNode;
    //链表的节点处理存储元素自身的信息外,还需要存放一个后继的指针。
    class Node{
    public:
    	int data;
    	Node* next;
    	Node(int x):data(x),next(NULL){}//构造函数,初始化data为x,next为NULL
    };

为了操作上的方便,在单链表第一个结点前附加一个结点,成为头结点。头结点的指针域指向线性表的第一个元素的结点。

void SLTPrint(SLNode* phead)
{
	SLNode* cur = phead;
	while (cur != NULL)
	{
		printf("%d->", cur->val);
		cur = cur->next;
	}
	printf("NULL\n");
}
 
SLNode* CreateNode(SLNDataType x)
{
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newnode->val = x;
	newnode->next = NULL;
	return newnode;
}
 
void SLTPushBack(SLNode** pphead, SLNDataType x)
{
	//assert(pphead);
 
	SLNode* newnode = CreateNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}
 
void SLTPushFront(SLNode** pphead, SLNDataType x)
{
	assert(pphead);
	SLNode* newnode = CreateNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}
 
void SLTPopBack(SLNode** pphead)
{
	assert(pphead);
	assert(*pphead);
 
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLNode* tail = *pphead;
		while (tail->next->next != NULL)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
	}
 
 
}
 
void SLTPopFront(SLNode** pphead)
{
	assert(pphead);
	assert(*pphead);
 
	SLNode* tmp = *pphead;
	*pphead = (*pphead)->next;
 
	free(tmp);
	
}
 
SLNode* SLTFind(SLNode* phead, SLNDataType x)
{
	SLNode* cur = phead;
	while (cur)
	{
		if (cur->val == x) return cur;
		else cur = cur->next;
	}
	return NULL;
}
 
void SLTInser(SLNode** pphead, SLNode* pos, SLNDataType x)
{
	//严格限定pos一定是链表中的一个有效节点
	assert(pphead);
	assert(pos);
	assert(*pphead);
 
	if (pos == *pphead)
	{
		SLTPushFront(pphead,x);
	}
	else
	{
		SLNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		SLNode* newnode = CreateNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}
 
void SLTErase(SLNode** pphead, SLNode* pos)
{
	assert(pphead);
	assert(pos);
	assert(*pphead);
 
	if (*pphead == pos)
	{
		SLTPopFront(pphead);
	}
	else
	{
		SLNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}
 
 
void SLTInsertAfter(SLNode* pos, SLNDataType x)
{
	assert(pos);
	SLNode* newnode = CreateNode(x);
 
	newnode->next = pos->next;
	pos->next = newnode;
}
 
void SLTEraseAfter(SLNode* pos)
{
	assert(pos);
	assert(pos->next);
 
	SLNode* tmp = pos->next;
	pos->next = pos->next->next;
	free(tmp);
	tmp = NULL;
}
 
void STLDestory(SLNode** pphead)
{
	assert(pphead);
 
	SLNode* cur = *pphead;
	while (cur)
	{
		SLNode* next = cur->next;
		free(cur);
		cur = next;
	}
	*pphead = NULL;
}
  • 双链表:双链表结点中有两个指针,分别指向其前驱结点和后继结点。
    typedef struct ListNode
    {
    	struct ListNode* next;//后继结点指针
    	struct ListNode* prev;//前驱结点指针
    	int data;
    }LTNode;
  • 循环链表:最后一个结点的指针不是NULL,改为指向头结点,从而使链表形成一个环。在循环双链表中,头结点的prve指针还要指向表尾结点。
  • 带头双向循环链表
    LTNode* BuyListNode(LTDataType x)
    {
    	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
    	if (node == NULL)
    	{
    		perror("malloc fail");
    		//return NULL;
    		exit(-1);
    	}
    	node->next = NULL;
    	node->prev = NULL;
    	node->data = x;
     
    	return node;
    }
     
    LTNode* LTInit()
    {
    	LTNode* phead = BuyListNode(-1);
    	phead->next = phead;
    	phead->prev = phead;
     
    	return phead;
    }
     
    void LTDestroy(LTNode* phead);
     
    void LTPrint(LTNode* phead)
    {
    	assert(phead);
     
    	printf("<=head=>");
    	LTNode* cur = phead->next;
    	while (cur != phead)
    	{
    		printf("%d<=>", cur->data);
    		cur = cur->next;
    	}
    	printf("\n");
    }
     
    bool LTEmpty(LTNode* phead)
    {
    	assert(phead);
     
    	/*if (phead->next == phead)
    	{
    		return true;
    	}
    	else
    	{
    		return false;
    	}*/
     
    	return phead->next == phead;
    }
     
     
    void LTPushBack(LTNode* phead, LTDataType x)
    {
    	assert(phead);
     
    	//LTNode* newnode = BuyListNode(x);
    	//LTNode* tail = phead->prev;
     
    	//// phead            tail  newnode
    	//tail->next = newnode;
    	//newnode->prev = tail;
    	//newnode->next = phead;
    	//phead->prev = newnode;
     
    	LTInsert(phead, x);
    }
     
    void LTPopBack(LTNode* phead)
    {
    	assert(phead);
    	assert(!LTEmpty(phead));
     
    	LTNode* tail = phead->prev;
    	LTNode* tailPrev = tail->prev;
     
    	tailPrev->next = phead;
    	phead->prev = tailPrev;
    	free(tail);
    	tail = NULL;
    }
     
    void LTPushFront(LTNode* phead, LTDataType x)
    {
    	assert(phead);
     
    	//LTNode* newnode = BuyListNode(x);
    	//LTNode* first = phead->next;
    	//phead->next = newnode;
    	//newnode->prev = phead;
     
    	//newnode->next = first;
    	//first->prev = newnode;
     
    	// 㻻˳
    	//newnode->next = phead->next;
    	//phead->next->prev = newnode;
     
    	//phead->next = newnode;
    	//newnode->prev = phead;
     
    	LTInsert(phead->next, x);
    }
     
    void LTPopFront(LTNode* phead)
    {
    	assert(phead);
    	assert(!LTEmpty(phead));
    	//....
    }
     
    void LTInsert(LTNode* pos, LTDataType x)
    {
    	assert(pos);
     
    	LTNode* prev = pos->prev;
    	LTNode* newnode = BuyListNode(x);
    	// prev newnode pos
     
    	prev->next = newnode;
    	newnode->prev = prev;
     
    	newnode->next = pos;
    	pos->prev = newnode;
    }

    顺序表 链表
    存取 (读写) 方式 顺序表可以顺序存取,也可以随机存取 链表只能从表头顺序存取元素
    逻辑结构与物理结构 采用顺序存储时,逻辑上相邻的元素,对应的物理存储位置也相邻 而采用链式存储时,逻辑上相邻的元素,物理存储位置不一定相邻, 对应的逻辑关系是通过指针链接 来表示的
    查找、插入和删除操作

    对于按值查找, 顺序表无序时,两者的时间复杂度均为 O(n);顺序表有序时, 可采用折半查找, 此时的时间复杂度为 O(log ₂n)。 对于按序号查找,顺序表支持随机访问,时间复杂度仅为O(1)

    顺序表的插入、删除操作,平均需要移动半个表长的元素

    链表按值查找的平均时间复杂度为O(n)

    链表的插入、删除操作,只需修改相关结点的指针域即可

    空间分配顺序 存储在静态存储分配情形下,需要预先分配足够大的存储空间。预先分配过大,可能会导致顺序表后部大量闲置; 预先分配过小, 又会造成溢出。动态存储分配虽然存储空间可以扩充,但需要移动大量元素, 操作效率降低,而且若内存中没有更大块的连续存储空间,则会导致分配失败 链式存储的结点空间只在需要时申请分配,只要内存有空间就可以分配, 操作灵活、高效。

    栈:只允许在一端进行插入和删除操作的线性表

    空栈:不含任何数据元素的栈

    栈顶:允许插入和删除的一端;插入:入栈、进栈、压栈。删除:出栈、弹栈

    栈底:另一端

    特性:先进后出。。。

    队列:只允许在表的一端插入,另一端删除的线性表;允许插入的一端叫队尾, 允许删除的一端叫队头。

    字符串:简称串,是由零个或多个字符组成的有限序列

    广义表:n ( >=0 )个表元素组成的有限序列

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐