顺序表与链表

顺序表和链表都是最最基本的数据结构,顺序表是数组形式来实现的,具有连续的物理结构,与逻辑结构。而链表则是在逻辑上连续,物理空间上不连续。

顺序表,利用动态开辟数组空间来实现。那么需要实现的接口有哪些呢。

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

typedef int data;

typedef struct seplist
{
	data* a;
	size_t size;
	size_t cap;
} SL;

void slpushfront(SL* ps, int x);

void slpushback(SL* ps,int x);

void slpopfront(SL* ps);

void slpopback(SL* ps);

void initseplist(SL*);

void selpistprint(SL*);

void checksl(SL*);

void slinsert(SL*, int pos, int x);

void slerase(SL*, int pos);

int slfind2(SL*, data x);

void destroy(SL*);

这里首先我们得有增删查改的基本实现。那么我们去思考,如何进行插入呢?
显然,如果尾部插入只需要将数据插入最后,尾删也同样如此,但是头插头删,是需要将数据挪到后面的。那么这样就会造成挪动的时间复杂度。

void initseplist(SL* ps)
{
	assert(ps);
	ps->cap = 5;
	ps->a = (data*)malloc(sizeof(data) *ps->cap);
	if (ps->a == NULL)
	{
		printf("创建失败\n");
		exit(-1);
	}
	ps->size = 0;
}

void checksl(SL* ps)
{
	assert(ps);
	if (ps->size == ps->cap)
	{
		ps->cap *= 2;
		ps->a = (data*)realloc(ps->a, sizeof(data) * ps->cap);
		if (ps->a == NULL)
		{
			printf("增容失败\n");
			exit(-1);
		}
	}
}


void selpistprint(SL* ps)
{
	assert(ps);
	int i = 0;
	for (i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->a[i]);
	}
}


void slpushback(SL* ps, int x)
{
	//assert(ps);
	//checksl(ps);
	//ps->a[ps->size] = x;
	//ps->size++;
	
	slinsert(ps, ps->size, x);
}


void slpopback(SL* ps)
{
	//assert(ps);
	//ps->size--;
	slerase(ps, ps->size - 1);
}


void slpushfront(SL* ps, int x)
{
	assert(ps);
	checksl(ps);
	slinsert(ps, 0, x);
	/*int end = 0;
	end = ps->size;
	while (end)
	{
		ps->a[end] = ps->a[end - 1];
		end--;
	}
	ps->a[0] = x;
	ps->size++;*/
}


void slpopfront(SL* ps)
{
	/*assert(ps);
	int i = 0;
	for (i = 0; i < ps->size; i++)
	{
		ps->a[i] = ps->a[i + 1];
	}
	ps->size--;*/

	slerase(ps, 0);
	
}


void slinsert(SL* ps, int pos, int x)
{
	assert(ps);
	assert(pos <= ps->size && pos >= 0);
	checksl(ps);
	int end = ps->size - 1;
	while (end >= pos)
	{
		ps->a[end + 1] = ps->a[end];
		end--;
	}

	ps->a[pos] = x;
	ps->size++;
}

void slerase(SL* ps, int pos)
{
	assert(ps);
	assert(pos < ps->size && pos >= 0);
	
	int start = pos;
	while (start < ps->size - 1)
	{
		ps->a[start] = ps->a[start + 1];
		start++;
	}
	ps->size--;
}

//data slfind(SL* ps, int pos)
//{
//	assert(ps);
//	data i = ps->a[pos - 1];
//
//	return i;
//}

int slfind2(SL* ps, data x)
{
	assert(ps);
	int i = 0;
	for (i = 0; i < ps->size; i++)
	{
		if (ps->a[i] == x)
			return i;
	}
	printf("没有查到\n");
	return 0;
}


void destory(SL* ps)
{
	free(ps->a);
	ps->a = NULL;
	ps->cap = ps->size = 0;
}

但是当我们去,完成任意位置的插入,那么就可以实现复用,那么就大大减少了代码的复杂度,这种思想在以后的实现中也是非常重要的,对重复使用代码的封装和相似代码的复用,可以大大减少代码量以增强可读性。

单链表 实现接口

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

typedef int SlistDataType;

typedef struct SlistNode
{
	SlistDataType data;
	struct SlistNode* next;

}SLN;


void Slistpushback(SLN** head, SlistDataType x);

void Slistpushfront(SLN** head, SlistDataType x);

void Slistpopback(SLN** head);

void Slistpopfront(SLN** head);

void Slistprint(SLN* head);

SLN* Slistfind(SLN* head,SlistDataType x);

void insertafter(SLN* pos, SlistDataType x);

void eraseafter(SLN* pos);

那么实现增删查改时,单链表有有何不同呢,首先就是很复杂~

#include "slist.h"

SLN* CreatnewNode(SlistDataType x)
{

	SLN* newNode = (SLN*)malloc(sizeof(SLN));
	if (newNode == NULL)
	{
		printf("申请失败\n");
		exit(-1);
	}
	newNode->data = x;
	newNode->next = NULL;
	return newNode;
}

void Slistprint(SLN* head)
{
	SLN* cur = head;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

void Slistpushback(SLN** head, SlistDataType x)
{
	SLN* newNode = CreatnewNode(x);
	if (*head == NULL)
	{
		*head = newNode;

	}
	else
	{
		SLN* tail = *head;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}

		tail->next = newNode;
	}

	
}

void Slistpopback(SLN** head)
{
	if (*head == NULL)
	{
		return;
	}
	else if ((*head)->next == NULL)
	{
		free(*head);
		*head = NULL;
	}
	else
	{
		SLN* pre = NULL;
		SLN* tail = *head;
		while (tail ->next != NULL)
		{
			pre = tail;
			tail = tail->next;
		}
		pre->next = NULL;
		free(tail);
		tail = NULL;
	}

}


void Slistpushfront(SLN** head, SlistDataType x)
{
	SLN* phead = CreatnewNode(x);
	if (*head == NULL)
	{
		*head = phead;
	}
	else
	{
		phead->next = *head;
		*head = phead;
	}
	

}


void Slistpopfront(SLN** head)
{
	if (*head == NULL)
	{
		return;
	}
	else if ((*head)->next == NULL)
	{
		free(*head);
		*head = NULL;
	}
	else
	{
		SLN* cur = *head;
		(*head) = (*head)->next;
		free(cur);
		cur = NULL;

	}
}

SLN* Slistfind(SLN* head,SlistDataType x)
{
	while (head)
	{
		if (head->data == x)
		{
			return head;
		}
		head = head->next;
	}
	return NULL;
}

void insertafter(SLN* pos, SlistDataType x)
{
	assert(pos);
	if (pos->next == NULL)
		return;
	SLN* newnode = CreatnewNode(x);
	SLN* cur = pos->next;
	pos->next = newnode;
	newnode->next = cur;
}

void eraseafter(SLN* pos)
{
	assert(pos);
	if (pos->next == NULL)
		return;
	SLN* nextnext;
	nextnext = pos->next->next;
	SLN* next = pos->next;
	pos->next = nextnext;
	free(next);
	next = NULL;
	
}

首先在增删查改的过程中,一定要小心空指针的解引用,空链表的情况也要讨论到,如像删除的情况下,那么删最后一个也是需要考虑到,那么如果你要改动头指针,那么传的指针也要是二级指针,并且,要保留前一个节点,不然怎么链接呢? 所以写这里的代码时候需要细细推敲细节。一般来说,prev cur next 三指针控制是很好的办法。

双向链表
双向带头循环链表,虽然来说结构很复杂,但是操作比单链表要简单太多了。
 插入图片描述
如图可见,如果存在一个头节点,那么显然,我们就不用传二级指针,也不用考虑空链表的情况了,加上了循环便可以很容易的找到尾节点,前后指针也可以去找到前面的节点。这样子的结构虽然复杂了,但是操作却简单了许多。

#pragma once

#include <stdio.h>
#include <assert.h>

typedef int LTDataType;

typedef struct ListNode
{
	struct ListNode* prev;
	struct ListNode* next;
	LTDataType data;
}ListNode;

ListNode* init();

ListNode* Creatnode(LTDataType x);

ListNode* printlist(ListNode* head);

void pushback(ListNode* head, LTDataType x);

void popback(ListNode* head);

void pushfront(ListNode* head, LTDataType x);

void popfront(ListNode* head);

ListNode* findlist(ListNode* head, LTDataType x);

void insert(ListNode* pos, LTDataType x);

void erase(ListNode* pos);

void clearlist(ListNode* head);

void destorylist(ListNode** head);



实现

#include "list.h"

ListNode* init()
{
	ListNode* head = Creatnode(0);
	head->next = head;
	head->prev = head;

	return head;
}

ListNode* printlist(ListNode* head)
{
	assert(head);
	ListNode* cur = head->next;
	while (cur != head)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}


ListNode* Creatnode(LTDataType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	newnode->next = NULL;
	newnode->prev = NULL;
	newnode->data = x;

	return newnode;
}


void pushback(ListNode* head, LTDataType x)
{
	assert(head);
	ListNode* newnode = Creatnode(x);
	ListNode* tail = head->prev;
	tail->next = newnode;
	head->prev = newnode;
	newnode->next = head;
	newnode->prev = tail;

	
}

void popback(ListNode* head)
{
	assert(head);
	if (head->next == head)
	{
		printf("链表无有效值\n");
		return head;
	}
	
	ListNode* tail = head->prev;
	head->prev = tail->prev;
	tail->prev->next = head;
	free(tail);
}


void pushfront(ListNode* head, LTDataType x)
{
	assert(head);
	ListNode* first = head->next;
	ListNode* newnode = Creatnode(x);
	head->next = newnode;
	first->prev = newnode;
	newnode->prev = head;
	newnode->next = first;

}

void popfront(ListNode* head)
{
	assert(head);

	if (head->next == head)
	{
		printf("链表无有效值\n");
		return head;
	}

	ListNode* first = head->next;
	head->next = first->next;
	first->next->prev = head;

	free(first);
}


ListNode* findlist(ListNode* head, LTDataType x)
{
	ListNode* cur = head->next;
	
	while (cur != head)
	{
		if (cur->data == x)
			return cur;
		cur = cur->next;
	}

	return NULL;
}


void insert(ListNode* pos, LTDataType x)
{
	assert(pos);
	ListNode* newnode = Creatnode(x);
	ListNode* posPrev = pos->prev;
	posPrev->next = newnode;
	newnode->next = pos;
	pos->prev = newnode;
	newnode->prev = posPrev;
}


void erase(ListNode* pos)
{
	assert(pos);
	ListNode* posPrev = pos->prev;
	ListNode* posNext = pos->next;
	posPrev->next = posNext;
	posNext->prev = posPrev;

	free(pos);
	pos = NULL;

}

void clearlist(ListNode* head)
{
	assert(head);
	ListNode* cur = head->next;
	while (cur != head)
	{
		ListNode* now = cur;
		cur = cur->next;
		free(now);
	}


}


void destorylist(ListNode** head)
{
	clearlist(*head);
	free(*head);
	*head = NULL;
}

顺序表与链表的优缺点
这里可以有个形象的比喻,一白遮百丑
顺序表的”白“在哪里呢,它可以支持随机访问,因为是由下标来确定的,所以这就是顺序表的最大优势,也是链表的最大劣势,因为随机访问不仅更灵活,还有在寄存器中命中率更高这样效率更高,
而链表的优势即顺序表的劣势,就是空间效率的利用率高,且不用移动数据,链表是要用一个开一个,且就是在哪里开,而数组还需要移动。

但是无论如何,两种数据结构各有优劣,所以可以互补,不一定要最好的,但是一定是最合适的~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值