线性表基础【C语言】

目录

概念

表的增删查改

查找(定位)元素:按值查找

顺序表新增/删除元素

(单)链表新增元素

(单)链表删除元素

顺序表更新元素

(单)链表删除元素

线性表集合式合并:只合并不同元素

合并两个有序表:本来分别有序,合并结果仍然有序

合并两个有序顺序表

合并两个有序链表

顺序表代码实现:增删查改

单链表代码实现:增删查改

带头双向循环链表

增删查改代码实现:


概念

线性表:指各数据元素间保持“1对1”关系的数据结构,分为顺序表和链表,分别通过元素相邻保存指针域的方式实现“1对1”

顺序表:是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存 储。在数组上完成数据的增删查改。“随机存取”

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

ps.

1.设顺序表有n个数组元素,则读取第i个数组元素的平均时间复杂度为:O(1)

2.单链表的存储密度<1

表的增删查改

查找(定位)元素:按值查找

  • 给定长度为n的线性表,查找值为v的元素
  • (最坏)从头到尾遍历=>时间复杂度O(n)

顺序表新增/删除元素

  • 给定长度为n的顺序表,在指定位置i插入一个新元素
  • 给定长度为n的顺序表,删除位置i的元素
  • 需要将位置i到位置n-1的所有元素都向后或向前挪一格
  • 在最坏情况下(i=0)下,需要挪动全部的n个元素=>时间复杂度为O(n)
  • 无需利用额外空间=>空间复杂度为O(1)

ps.

  1. 设顺序表中有n个元素,往其插入或从其删除一个元素,平均要移动的元素个数为 _____
    答案:n/2
  2. 设顺序表中有n个元素,则时间复杂度为O(1)的操作是(  )

         A.访问第i个结点(i∈[1,n])                                      B.删除第i个结点(i∈[1,n])

         C.在第i个结点后插入一个新元素(i∈[1,n])            D.将顺序表从小到大排序 

答案:A

(单)链表新增元素

  • 给定长度为 n 的单链表,在第i个结点插入一个新元素
  • 首先需要从头结点开始逐个向后找i-1次=>时间复杂度为O(n)
  • 找到后插入只需要修改第i-1个结点和待插入结点的[后继结点地址]即可=>O(1)
  • 无需利用额外空间=>空间复杂度为O(1)

(单)链表删除元素

  • 给定长度为 n 的单链表,删除第i个结点
  • 需要移动到第i个结点的前驱结点,最坏情况下移动n-1次
  • 修改前驱结点的后继指针=>O(1)
  • 无需利用额外空间=>空间复杂度为O(1)

顺序表更新元素

  • 给定长度为n的顺序表,更新位置i的元素
  • 无论i的值如何,都可以通过i直接访问位置i元素,将其更新为v'=>时间复杂度为O(1)=>随机存取

(单)链表删除元素

  • 给定长度为 n 的单链表,更新第i个结点的值
  • 需要从头结点开始一个接一个找到第i个才能访问并更新它=>顺序存取
  • 最坏情况遍历整个链表=>时间复杂度为O(n)

ps.

1.单链表的增删元素虽然不用移动元素,但需先找到其前驱结点,复杂度为O(n)

2.若线性表需要频繁更新元素   ->选择顺序表

3.若线性表需要频繁插入删除元素  ->选择链式表

(1)下列说法正确的是()

A.如果插入操作频繁发生在表头部,顺序表和单链表效率接近

B.如果插入操作频繁发生在表头部,顺序表比单链表效率更高

C.如果插入操作频繁发生在表尾部,顺序表比单链表效率更高

D.如果插入操作频繁发生在表尾部,顺序表和单链表效率接近

答案:C

线性表集合式合并:只合并不同元素

A:7,5,3,11

B:2,7,6,3

合并后:C:7,5,3,11,2,6

  • 设A表长度为n,B表长度为m
  • 对于B表中的每个元素,都需要先判断其是否已经存在A里=>O(mn)
  • 如果存在,无需插入,如果不存在,将其插入在A的末尾=>O(1)
  • 总时间复杂度为O(mn)
  • 空间复杂度?顺序表:O(m+n)、链表:O(1)

合并两个有序表:本来分别有序,合并结果仍然有序

A:3,5,8,11

B:2,6,7,10

C:2,3,5,6,7,8,10,11

合并两个有序顺序表

  • 设A表长度为n,B表长度为m
  • 先预留结果表空间:n+m个元素
  • 从表头开始同时逐个访问A表和B表元素,将当前位置上较小者放入结果表并后移一位
  • 总时间复杂度为O(m+n)
  • 空间复杂度为O(m+n)

合并两个有序链表

  • 设A表长度为n,B表长度为m
  • 先创建一个头结点(哑结点dummy),其数据没有实际意义,只为用它的[指针域]
  • 从表头开始逐个同时遍历A和B,将当前已完成合并的表尾元素的后继节点设置为当前A和B游标中较小个,并将该游标向后移动一位
  • 时间复杂度为O(m+n)、空间复杂度为O(1)

ps.

1.合并两个有序表:逐一比较两表当前元素,将正确的元素添加进结果表并移动游标

(1)合并两个长度为n的有序表,可能的最小比较次数为()

A.2n            B.2n-1        C.n       D.1

答案:C

分析:在合并两个有序表时,当一表游标移动至表尾时,直接将另一表剩下元素整体接在结果表后即可

顺序表代码实现:增删查改

//SeqList.h
typedef int SLDataType;
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef struct SeqList
{
	SLDataType* a;//指向动态开辟数组
	int size;//记录存储多少个有效数据
	int capacity;//空间容量大小
}SL;

void SLInit(SL* ps);
void SLDestroy(SL* ps);
void SLCheckCapacity(SL* ps);
void SLPushBack(SL* ps, int x);
void SLPopBack(SL* ps);
void SLPrint(SL* ps);
void SLPushFront(SL* ps, SLDataType x);
void SLPopFront(SL* ps);
//中间插入删除
//在pos位置插入数据
void SLInsert(SL* ps, int pos, SLDataType x);
//删除pos位置数据
void SLErase(SL* ps, int pos);
//int SLFind(SL* ps, SLDataType x);
//begin查找x的起始位置
int SLFind(SL* ps, SLDataType x,int begin);



//SeqList.c
#include"SeqList.h"
//顺序表初始化
void SeqListInit(SeqList* ps)
{
	assert(ps);
	ps->a = NULL;
	ps->size = 0;
	ps->capacity = 0;
}

//顺序表置空
void SeqListDestroy(SeqList* ps)
{
	if (ps->a)
	{
		free(ps->a);
		ps->a = NULL;
		ps->capacity = ps->size = 0;
	}
}

//顺序表打印
void SeqListPrint(SeqList* ps)
{
	for (int i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->a[i]);
	}
	printf("\n");
}

//顺序表尾插
void SeqListPushBack(SeqList* ps, SLDateType x)
{
	SeqListInsert(ps, ps->size, x);
}

//顺序表头插
void SeqListPushFront(SeqList* ps, SLDateType x)
{
	SeqListInsert(ps, 0, x);
}


//顺序表头删
void SeqListPopFront(SeqList* ps)
{
	SeqListErase(ps, 0);

}

//顺序表尾删
void SeqListPopBack(SeqList* ps)
{
	SeqListErase(ps, ps->size-1);
}

// 顺序表查找
int SeqListFind(SeqList* ps, SLDateType x)
{
	assert(ps);
	for (int i = 0; i < ps->size; i++)
	{
		if (ps->a[i] == x)
		{
			return i;
		}
	}
	return -1;
}

// 顺序表在pos位置插入x
void SeqListInsert(SeqList* ps, int pos, SLDateType x)
{
	assert(ps);
	assert(pos >= 0);
	assert(pos <= ps->size);
	if (ps->size == ps->capacity)
	{
		int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		SLDateType* tmp = (SLDateType*)realloc(ps->a, newCapacity * sizeof(SLDateType));
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}
		ps->a = tmp;
		ps->capacity = newCapacity;
	}
	int end = ps->size - 1;
	while (end >= pos)
	{
		ps->a[end + 1] = ps->a[end];
		--end;
	}
	ps->a[pos] = x;
	ps->size++;
}

// 顺序表删除pos的值
void SeqListErase(SeqList* ps, int pos)
{
	assert(ps);
	assert(pos >= 0);
	assert(pos < ps->size);
	int begin = pos + 1;
	while (begin < ps->size)
	{
		ps->a[begin - 1] = ps->a[begin];
		begin++;
	}
	ps->size--;
}


//test.c
#include "SeqList.h"
int main()
{
	SeqList s;
	SeqListInit(&s);
	printf("尾插:1,2,3,4,5\n");
	SeqListPushBack(&s, 1);
	SeqListPushBack(&s, 2);
	SeqListPushBack(&s, 3);
	SeqListPushBack(&s, 4);
	SeqListPushBack(&s, 5);
	SeqListPrint(&s);
	printf("尾删5\n");
	SeqListPopBack(&s);
	SeqListPrint(&s);
	printf("头插1\n");
	SeqListPushFront(&s, 1);
	SeqListPrint(&s);
	printf("头删1\n");
	SeqListPopFront(&s, 1);
	SeqListPrint(&s);
	printf("查找2并删除\n");
	int pos = SeqListFind(&s, 2, 0);
	if (pos != -1)
	{
		SeqListErase(&s, pos);
	}
	SeqListPrint(&s);
	return 0;
}

单链表代码实现:增删查改

//SList.h
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

typedef int SLTDateType;
typedef struct SListNode
{
	SLTDateType data;
	struct SListNode* next;
}SListNode;

// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x);
// 单链表打印
void SListPrint(SListNode* plist);
// 单链表尾插
//ps.需要修改头指针用二级指针,不需要修改用一级指针,只读不用指针
void SListPushBack(SListNode** pplist, SLTDateType x);
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x);
// 单链表的尾删
//链表为空不可以删
void SListPopBack(SListNode** pplist);
// 单链表头删
//链表为空不可以删
void SListPopFront(SListNode** pplist);
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x);
// 单链表在pos位置之后插入x
// 分析思考为什么不在pos位置之前插入?
void SListInsertAfter(SListNode* pos, SLTDateType x);
// 单链表删除pos位置之后的值
// 分析思考为什么不删除pos位置?
void SListEraseAfter(SListNode* pos);
// 单链表的销毁
void SListDestroy(SListNode** plist);
//创建一个链表
SListNode* CreateSList(int n);





//SList.c
#include"SList.h"
//总结:
// 1.改变int,传递int*给形参,*形参进行交换改变
// 2.改变int*,传递int**给形参,*形参进行交换改变 
// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x)
{
	SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

SListNode* CreateSList(int n)
{
	SListNode* phead = NULL, * ptail = NULL;
	for (int i = 0; i < n; ++i)
	{
		SListNode* newnode = BuySListNode(i);
		if (phead == NULL)
		{
			phead = ptail = newnode;
		}
		else
		{
			ptail->next = newnode;
			ptail = newnode;
		}
	}
	return phead;
}

// 单链表打印
void SListPrint(SListNode* plist)
{
	SListNode* cur = plist;
	while (cur != NULL)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x)
{
	SListNode* newnode = BuySListNode(x);
	if (*pplist == NULL)
	{
		*pplist = newnode;
	}
	else
	{
		SListNode* tail = *pplist;
		//找尾
		while (tail->next)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

// 单链表的尾删
void SListPopBack(SListNode** pplist)
{
	assert(*pplist);
	if ((*pplist)->next == NULL)
	{
		free(*pplist);
		*pplist = NULL;
	}
	/*SListNode* prev = NULL;
	SListNode* tail = pplist;
	while (tail->next)
	{
		prev = tail;
		tail = tail->next;
	}
	prev->next = NULL;*/
	SListNode* tail = *pplist;
	while (tail->next->next)
	{
		tail = tail->next;
	}
	tail->next = NULL;
}

void SListPushFront(SListNode** pplist, SLTDateType x)
{
	SListNode* newnode = BuySListNode(x);
	newnode->next = *pplist;
	*pplist = newnode;
}

void SListPopFront(SListNode** pplist)
{
	assert(*pplist);
	SListNode* next = (*pplist)->next;
	free(*pplist);
	*pplist = next;
}

SListNode* SListFind(SListNode* plist, SLTDateType x)
{
	SListNode* cur = plist;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

void SListInsertAfter(SListNode* pos, SLTDateType x)
{
	assert(pos);
	SListNode* newnode = BuySListNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

void SListEraseAfter(SListNode* pos)
{
	assert(pos);
	if (pos->next == NULL)
	{
		return;
	}
	else
	{
		SListNode* nextNode = pos->next;
		pos->next = nextNode->next;
		free(nextNode);
	}
}

void SListDestroy(SListNode** pplist)
{
	SListNode* cur = *pplist;
	while (cur)
	{
		SListNode* next = cur->next;
		free(cur);
		cur = next;
	}
	*pplist = NULL;
}




//test.c
#include"SList.h"

void TestList1()
{
	SListNode* plist = CreateSList(10);
	SListPrint(plist); 
	SListNode* pos = SListFind(plist, 5);
	if (pos)
	{
		printf("找到了\n");
	}
	else
	{
		printf("找不到\n");
	}
	SListNode* p = SListFind(plist, 2);
	SListInsertAfter(p, 20);
	SListPrint(plist);
}

void TestList2()
{
	SListNode* plist = NULL;
	SListPushBack(&plist, 100);
	SListPushBack(&plist, 200);
	SListPushBack(&plist, 300);
	SListPopBack(&plist);
	SListPrint(plist);
	SListPushFront(&plist, 1);
	SListPrint(plist);
	SListPopFront(&plist);
	SListPrint(plist);
}

int main()
{
	TestList1();
	return 0;
}

带头双向循环链表

1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结 构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。

2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都 是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带 来很多优势,实现反而简单了

头插:

尾插:

尾删:

增删查改代码实现:

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;
} 

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

void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTInsert(phead, x);
}
void LTPopBack(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	LTErase(phead);
}

void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTInsert(phead->next, x);
}

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

void LTPopFront(LTNode* phead)
{
	LTErase(phead->next);
}

LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* prev = pos->prev;
	LTNode* newnode = BuyListNode(x);
	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;
}
 
void LTErase(LTNode* pos)
{
	assert(pos);
	LTNode* prev = pos->prev;
	LTNode* next = pos->next;
	free(pos);
	prev->next = next;
	next->prev = prev;
}

bool LTEmpty(LTNode* phead)
{
	assert(phead);
	return phead->next == phead;
}

size_t LTSize(LTNode* phead)
{
	assert(phead);
	size_t size = 0;
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		++size;
		cur = cur->next;
	}
	return size;
}

void LTDestroy(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = cur->next;
	}
	free(phead);
	phead = NULL;
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值