顺序表和链表

1.线性表

  1.什么是线性表?

如果我们想要存储数据,最常见的就是数据与数据之间一一联系,前一个数据和后一个数据相连,这种数据结构最后会像线一样连成一串,这种关系逻辑被称为线性结构,线性表根据物理结构分为顺序表和链表,顺序表的物理地址是连续的,链表的物理地址使用一根线连接

2. 顺序表

1.什么是顺序表?

顺序表是用一段物理地址连续的储存单元依次储存元素的线性结构,一般情况下采用数组储存,顺序表可以分为静态顺序表和动态顺序表

2.静态顺序表

我们可以创建一个结构体,结构体成员包括数组arr和记录数据个数size,每将一个数据储存到数组中,size的大小加1,静态顺序可以容纳数据的大小在数组创建时就已经决定

#define N 10                            //通过更改N的值改变数组的长度

typedef int SLDataType         //可以把int改为数据类型

typedef struct SeqList

{

    SLDataType arr[N];            //数组的长度固定

    int size;

}SeqList;

3.动态顺序表

静态顺序表只适用于确定知道要存储多少数据的时候,静态顺序表的定长数组,如果数组过大浪费空间,如果数组过小不够用,所以动态顺序表更加实用,根据需要的数据大小动态分配大小

关于动态内存管理的内容可以参考:(c语言)动态内存管理​​​​​​

  • 创建一个结构体struct SeqList,结构体成员包括一个指向动态开辟数组的指针a,有效数据的个数size,数据容量空间的大小capacity
typedef int SLDataType;
typedef struct SeqList
{
	SLDataType* a;//指向动态开辟数组
	int size;     //有效数据的个数
	int capacity; //容量空间的大小
}SeqList;
  • 创建一个函数void InitSqList(SeqList* pc)初始化顺序表,断言指针pc不为空,通过malloc函数申请Capacity个整形大小空间,初始化pc->size=0,pc->capacity=Capacity
//顺序表的初始化
void InitSqList(SeqList* pc)
{
	assert(pc);
	SLDataType* p = NULL;
	p = (SLDataType*)malloc(Capacity * sizeof(SLDataType));
	if (pc == NULL)
	{
		perror("InitSqList");
		exit(-1);
	}
	pc->a = p;
	pc->size = 0;
	pc->capacity = Capacity;
}
  • 创建一个函数void DestorySqList(SeqList* pc)销毁顺序表,断言指针pc不为空,释放pc->a指向的空间并将指针置空,将pc->size=pc->capacity=0
//顺序表的销毁
void DestorySqList(SeqList* pc)
{
	assert(pc->a);
	free(pc->a);
	pc->a = NULL;
	pc->size = 0;
	pc->capacity = 0;
}
  • 创建一个函数void PrintSqList(SeqList* pc)打印顺序表,遍历并打印pc->a的元素
//打印顺序表
void PrintSqList(SeqList* pc)
{
	assert(pc);
	int i = 0;
	for (i = 0; i < pc->size; i++)
	{
		printf("%d ", pc->a[i]);
	}
	printf("\n");
}
  • 创建一个函数void CheckCapacity(SeqList* pc)检查容量是否为满,判断如果pc->size==p->capacity,使用realloc函数扩容为两倍原容量
//检查容量是否已满、
#define Capacity 4
void CheckCapacity(SeqList* pc)
{
    assert(pc);
	if (pc->capacity == pc->size)
	{
		SLDataType* tmp = (SLDataType*)realloc(pc->a, 2 * (pc->capacity) * sizeof(SeqList));
		if (tmp == NULL)
		{
			perror("CheckCapacity");
			exit(-1);
		}
		pc->a = tmp;
		pc->capacity *= 2;
		//printf("增容成功\n");
	}
}
  • 创建一个函数void SqListInsert(SeqList* pc, int pos, int val),断言插入的位置pos>=0&&pos<=pc->size,首先使pc->size++,使用CheckCapacity函数检查是否容量已满,然后从后往前挪动覆盖数据(从前往后挪动数据,后面的数据会被覆盖),最后p->arr[pos]=val

在挪动覆盖数据时注意:创建一个变量end用于遍历pos之前的元素,在此之前pc->size++,所以将end赋初值为pc->size-2,让pc->a[end + 1] = pc->a[end], 每次end--直到end>=pos

//在指定位置插入元素
void SqListInsert(SeqList* pc, int pos, int val)
{
	assert(pos >= 0 && pos <= pc->size);
	assert(pc);
	pc->size++;
	CheckCapacity(pc);
	int end = pc->size-2;
	while (end>=pos)
	{
		pc->a[end + 1] = pc->a[end];
		end--;
	}
	pc->a[pos] = val;
}
  • 创建一个函数void SqListDel(SeqList* pc, int pos),pos >= 0 && pos < pc->size,从前向后挪动覆盖数据,最后pc->size--

在挪动覆盖数据时注意:创建一个变量end用于遍历pos之前的元素,将end赋初值为pc->size,让pc->a[end] = pc->a[end + 1], 每次end++直到end<pc->size-1


//弹出指定位置的元素
void SqListDel(SeqList* pc, int pos)
{
	assert(pos >= 0 && pos < pc->size);
	assert(pc);
	int end = pos;
	while (end<pc->size-1)
	{
		pc->a[end] = pc->a[end + 1];
		end++;
	}
	pc->size--;
}
  • 创建SqListPushback函数在尾部插入元素时,直接使用SqListInsert(pc, pc->size, x)
  • 创建SqListPushFront函数在首部插入元素时,直接使用SqListInsert(pc, 0, x)
  • 创建SqListPopBack函数弹出尾部元素时,直接pc->size--即可,不会影响后续各种操作并且足够简洁
  • 创建SqListPopFront函数弹出首部元素时,直接使用SqListDel(pc,0)

//在尾部插入元素
void SqListPushback(SeqList *pc, SLDataType x)
{
	SqListInsert(pc, pc->size, x);
}

//弹出尾部元素
void SqListPopBack(SeqList* pc)
{
	assert(pc->size > 0);
	assert(pc);
	pc->size--;
}

//在首部插入元素
void SqListPushFront(SeqList* pc, SLDataType x)
{
	SqListInsert(pc, 0, x);
}

//弹出首部元素
void SqListPopFront(SeqList* pc)
{
	assert(pc->size > 0);
	assert(pc);
    SqListDel(pc,0);
}

3.单链表

顺序表在中间或头部的插入删除,需要把数据向后挪动,时间复杂度为O(N)

扩容空间使用realloc函数,如果原空间后面空间不足,需要把原数据拷贝到新空间,再释放原空间,会有不小的消耗

扩容一般是以2倍空间扩容,会有一定空间的浪费。假如空间是100,扩容两倍空间变成200,我们使用了101空间,剩余99空间

 链表的物理储存结构是非连续的,非顺序的储存结构,数据元素的逻辑顺序是通过链表的指针链接实现的,这样就可以解决顺序表中间插入时间复杂度高和空间浪费的问题

  •  创建一个结构体struct SlistNode相当于一个链表节点,一个链表储存了SLTData类型的变量和下一个链表节点的地址,通过解引用就可以访问下一个节点
  • 创建头指针,头指针指向第一个节点

typedef int SLTData;
typedef struct SListNode
{
	SLTData Data;
	struct SListNode* next;
}SLTNode;

//主函数
void test()
{
	SLTNode* head = NULL;//创建头指针,头指针指向第一个节点
	int j = 0;
	printf("请输入想要输入数据的个数:>");
	scanf("%d", &j); 
	int i = 0;
	printf("请依次输入数据:>");
	for (i = 0; i < j; i++)
	{
		int x = 0;
		scanf("%d", &x);
		SLTPushBack(&head, x);
	}
	SLTPrint(phead);
	SListInsertAfter(&head, head->next, 10);
	SListInsert(&head, head->next, 20);
	SLTPrint(head);
}
int main()
{
	test();
	return 0;
}
  • 创建SLTNode* AddSLTNode()函数用于增加一个新的节点,使用malloc函数申请一个STLNode大小的空间,将这个新节点NewNode->next=NULL,返回指向该空间的指针
//增加一个新的节点
SLTNode* AddSLTNode()
{
	SLTNode* NewNode = (SLTNode*)malloc(sizeof(SLTNode));
	if (NewNode == NULL)
	{
		perror("AddSLTNode");
		exit(-1);
	}
	NewNode->next = NULL;
	return NewNode;
}
  •  创建void SLTPrint(SLTNode* plist)函数打印所有节点,打印每一个节点的plist->Data,每次打印plist=plist->next先后移东一个节点直到plist为空,最后打印NULL空指针表示停止
//打印所有节点
void SLTPrint(SLTNode* plist)
{
	while (plist)
	{
		printf("%d->", plist->Data);
		plist = plist->next;
	}
	printf("NULL\n");

}
  •  创建void SLTPushFront(SLTNode** phead, SLTData x)函数头插,注意:因为需要修改phead形参类型为二级指针,首先申请一个新的节点NewNode,NewNode->Data=x如果phead为NULL,*phead直接指向NewNode,否则让NewNode->next = *phead指向头节点,*phead = NewNode指向新节点

//头插
void SLTPushFront(SLTNode** phead, SLTData x)
{
	assert(phead);
	SLTNode* NewNode = AddSLTNode();
	NewNode->Data = x;
	if(*phead==NULL)
	    *phead = NewNode;
	else
	{
		NewNode->next = *phead;
		*phead = NewNode;
	}

}
  •  创建void SLTPopFront(SLTNode** phead)函数头删(我们释放头节点,让头指针指向下一个节点,如果我们直接释放头节点无法找到下个节点,所以我们需要一个指针储存头指针,头指针移动到下一个节点)创建一个plist = *phead,*phead = (*phead)->next指向下一个节点,最后free(plist)释放头节点

//头删
void SLTPopFront(SLTNode** phead)
{
	assert(phead);
	assert(*phead);
	SLTNode* plist = *phead;
	*phead = (*phead)->next;
	free(plist);
}
  • 创建 void SLTPushBack(SLTNode** phead, SLTData x)函数尾插,首先申请一个新的节点NewNode,NewNode->Data=x如果phead为NULL,*phead直接指向NewNode,否则找到指向最后一个尾节点的指针,让尾节点plist->next = NewNode指向新节点(创建指针plist=*phead,每次plist=plist->next直到plist->next为空)

//尾插
void SLTPushBack(SLTNode** phead, SLTData x)
{
	assert(phead);
	SLTNode* NewNode = AddSLTNode();
	NewNode->Data = x;
	SLTNode* plist = *phead;
	if (*phead == NULL)
	{
		*phead = NewNode;
	}
	else
	{
		while (plist->next)
		{
			plist = plist->next;
		}
		plist->next = NewNode;
	}

}
  •  创建SLTNode* SListFind(SLTNode* phead, SLTData x)函数查找某个数,遍历列表每次phead = phead->next直到找到phead->Data == x或者phead=NULL退出循环返回空指针

//查找某个数
SLTNode* SListFind(SLTNode* phead, SLTData x)
{
	assert(phead);
	while (phead)
	{
		if (phead->Data == x)
		{
			return phead;
		}
		phead = phead->next;	
	}
	return phead;
}
  •  创建void SListInsert(SLTNode** phead, SLTNode* pos, SLTData x)函数在pos位置之前插入x,如果*phead==pos相当于头插直接SLTPushFront(phead, x),每次plist = plist->next直到plist->next=pos,然后创建一个新节点NewNode->Data = x,NewNode->next = plist->next,plist->next = NewNode
//在pos位置之前插入x
void SListInsert(SLTNode** phead, SLTNode* pos, SLTData x)
{
	assert(phead);
	assert(pos);
	if (*phead == pos)
	{
		SLTPushFront(phead, x);
	}
	else
	{
		SLTNode* plist = *phead;
		while (plist->next!=pos)
		{
			plist = plist->next;
		}
		SLTNode* NewNode = AddSLTNode();
		NewNode->Data = x;
		NewNode->next = plist->next;
		plist->next = NewNode;
	}
}
  •  创建void SListInsertAfter(SLTNode** phead, SLTNode* pos, SLTData x)函数在pos位置之后插入,首先创建一个新节点NewNode,NewNode->next = pos->next新节点链接pos指向节点的下一个节点,pos->next = NewNode指针pos指向的节点指向NewNode
//在pos位置之后插入
void SListInsertAfter(SLTNode** phead, SLTNode* pos, SLTData x)
{
	SLTNode* NewNode = (SLTNode*)malloc(sizeof(SLTNode));
	NewNode->Data = x;
	NewNode->next = pos->next->next;
	pos->next = NewNode;
}

总结:

  1. SLTNode* AddSLTNode()函数用于增加一个新的节点,函数的作用就是单纯创建一个新的节点,并返回指向该节点的指针,NewNode->next=NULL这个保证了后面的函数void SLTPrint(SLTNode* plist)需要找到NULL
  2. 头插和尾插都需要考虑链表中有没有元素,如果插入的是第一个元素(即*phead==NULL),需要修改*phead=NewNode(即修改头节点head)

如果想要修改头指针head需要取head的地址,将二级指针传参给函数,void SLTPushBack(SLTNode* phead, SLTData x)这样定义函数,改变phead是无法改变真正的头指针的,改变的是形参、

4.双向带头链表

哨兵位:该节点是个无效节点,不存储任何有效信息,但使用它可以方便我们头尾插和头尾删时不用判断头节点指向NULL的情况,同时也不需要改变头指针的指向,也就不需要传二级指针了。 

  • 创建一个结构体,结构体成员为指向上一个节点的指针prev,指向下一个节点的指针next,存放LTDatetype类型数据的变量Data 
typedef  int LTDatatype;

typedef struct ListNode
{
	struct ListNode* prev;
	struct ListNode* next;
	LTDatatype Data;

}ListNode;

//主函数
TestList3()
{
	ListNode* phead = InitList();
	STListPushBack(phead, 1);
	STListPushFront(phead, 10);
	STListPushFront(phead, 20);
	STListPrint(phead);
	ListNode* pos = STListFind(phead, 10);
	STListInsert(phead, pos, 30);
	STListPrint(phead);
	STListErase(phead, pos);
	STListPrint(phead);
	free(phead);
}

int main()
{
	TestList3();
	return 0;
}
  •  创建SLTNode* AddSLTNode()函数用于增加一个新的节点,使用malloc函数申请一个STLNode大小的空间,返回指向该空间的指针

ListNode* BuySTList()
//创建一个新的节点
{
	ListNode* NewNode = (ListNode*)malloc(sizeof(ListNode));
	return NewNode;
}
  •  创建ListNode* InitList()函数初始化哨兵位,让phead->next = phead,phead->prev = phead全部指向phead哨兵位自己,同时phead->Data=0存放的数据为0,返回哨兵位

ListNode* InitList()
//初始化哨兵位
{
	ListNode* phead = (ListNode*)malloc(sizeof(ListNode));
	phead->next = phead;
	phead->prev = phead;
	phead->Data = 0;
	return phead;
}
  • 创建 ListNode* DestorySTList(ListNode* phead)函数销毁所有数据,遍历链表中所有节点,依次释放空间
ListNode* DestorySTList(ListNode* phead)
//销毁所有数据
{
	assert(phead->next);
	phead = phead->next;
	ListNode* first = phead;
	while (first!=phead)
	{
		phead = phead->next;
		free(first);
		first = phead;
	}

}
  • 创建void STListPrint(ListNode* phead)函数打印节点,依次遍历每一个节点,打印每一个节点的数据
void STListPrint(ListNode* phead)
//打印节点
{
	assert(phead);
	ListNode* first = phead->next;
	printf("phead=>");
	while (first!=phead)
	{
		printf("%d<=>", first->Data);
		first = first->next;
	}
	printf("\n");
}
  • 创建ListNode* STListFind(ListNode* phead, LTDatatype x)函数查找节点,还是像上面一样遍历所有节点,如果找到first->Data == x,返回指向该节点的指针
ListNode* STListFind(ListNode* phead, LTDatatype x)
//查找节点
{
	assert(phead);
	ListNode* first = phead->next;
	while (first != phead)
	{
		if (first->Data == x)
			return first;
		first = first->next;
	}
	return NULL;
}
  • 创建void STListInsert(ListNode* phead, ListNode* pos, LTDatatype x)函数在pos前位置插入节点,首先创建一个新节点NewNode,通过pos->prev找到pos上一个节点,first->next = NewNode;NewNode->prev = first,NewNode->next = pos,pos->prev = NewNode
void STListInsert(ListNode* phead, ListNode* pos, LTDatatype x)
//在pos前位置插入节点
{
	assert(phead);
	assert(pos);
	ListNode* NewNode = BuySTList();
	NewNode->Data = x;
	ListNode* first = pos->prev;
	first->next = NewNode;
	NewNode->prev = first;
	NewNode->next = pos;
	pos->prev = NewNode;
}
  • 创建void STListErase(ListNode* phead, ListNode* pos)函数删除pos位置的节点,直接通过pos->prev和pos->next找到pos上一个节点和下一个节点,first->next = second,second->prev = first
void STListErase(ListNode* phead, ListNode* pos)
//删除pos位置的节点
{
	assert(phead);
	assert(phead->next != phead);
	ListNode* first = pos->prev;
	ListNode* second = pos->next;
	free(pos);
	first->next = second;
	second->prev = first;
}
  •  头插尾插直接使用void STListInsert(ListNode* phead, ListNode* pos, LTDatatype x)函数
  • 头删尾删直接使用void STListErase(ListNode* phead, ListNode* pos)函数

void STListPushBack(ListNode* phead, LTDatatype x)
//尾插
{
	STListInsert(phead, phead->prev, x);
}

void STListPopBack(ListNode* phead)
//尾删
{
	STListErase(phead, phead->prev);
}

void STListPushFront(ListNode* phead, LTDatatype x)
//头插
{
	STListInsert(phead, phead->next, x);
}

void STListPopFront(ListNode* phead)
//头删
{
	STListErase(phead, phead->next);
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值