顺序表与链表(上)

目录

线性表

顺序表

链表

顺序表与链表的区别

顺序表具体情况讲解


线性表

这里要讲的顺序表和链表都属于线性表。

线性表它的逻辑结构是呈线性的,也就是单线串联下去的简单结构。

 我们所说的顺序表、链表、栈、队列、字符串都是线性表,在逻辑上是呈线性串成的。

表,除了逻辑结构,还有物理结构。逻辑结构就是设计时候想象的情况,比如顺序表一开始想象的时候是像数组一样按顺序往后排的,链表是一个个串起来的样子.......

 我们说是不是线性表也是看它的逻辑结构。

物理结构是实际使用的时候,呈现出的连接方式,可能逻辑结构是线性的,但物理结构就不是了。

顺序表

顺序表的逻辑结构和物理结构是一致的。

顺序表实质上是数组,但数组是静态的,而顺序表可以是静态的也可以是动态的,它是动态增长的,并且要求里面存放的数据是从左往右连续的。

比如说:创建出一个数组,可以在数组内任何位置存放数据,但是顺序表只能按顺序存入数据。

 为什么需要顺序表呢?数组创建只能一次创建固定的空间,比如arr[10],如果需要存100个数据,但是初始化的时候不知道不要多大空间,创建了1000就浪费了,创建了50就不够,这是数组的缺陷,所以需要顺序表。

顺序表也是有缺陷的:1、如果顺序表初始化空间不够后续增加数据需要增容,但这种动态增容是有性能消耗的(如果后面没有足够的空间,开辟新空间拷贝数据,释放原空间是有消耗的)   。

    2、顺序表也是存在空间浪费的,动态扩容一次是扩容原空间两倍的空间的,比如原来是10个字节大小的空间,现在存放的数据满了,要存入再放入1个字节大小的数据,这时就扩容到20字节(2倍),这也满了,再扩容到40字节.......每次都2倍,有可能会造成一定的浪费,但这其实浪费不多,所以一般考虑较少。

3、如果需要在头部插入数据的话,需要挪动数据,将所有数据往后挪动1位,这样做是有代价的,空间复杂度变成了O(N)。

因此,有人发明了新的结构:链表

链表

链表一般是作用在结构体里面的,它是将一块一块的数据像链条一样串起来。

比如要存一个数据5,它由两部分构成:一个是结构体数据5,后面是一个指针,指向下个节点。

 这种结构有他的优势:1、不需要动态扩容,需要插入数据直接创建,指针指向一个节点就连起来了          2、插入头指针不用挪动数据,用头指针指向第一个节点就行了。

 但链表也有缺陷,在这里先不说了。

链表的物理结构与逻辑结构不同,不是呈线性的,因为它可以通过指针任意指向节点。

        

顺序表与链表的区别

上面在介绍的过程中其实就讲了区别了 。

1、顺序表有动态增容,链表没有,所以顺序表经常会有些许空间浪费,链表因为是指针指向节点所以不存在。

2、顺序表头部插入数据需要挪动整个数据,链表不需要,只需要用头指针指向第一个节点就行了。

顺序表具体情况讲解

为了方便起见,创立工程后,用3个文件来实现顺序表的不同功能。

头文件里放结构体定义和函数头,SeqList.c实现顺序表不同功能,test.c用于测试。

顺序表一般都是动态的,静态的用处不大,简单说一下。

//静态顺序表
#define N 100
struct SeqList
{
	int a[N];
	int size;
};

 主要还是来看动态顺序表。

struct SeqList
{
	int *a;        //指针指向顺序表的头
	int size;      //size是有效数据的个数
	int capacity;  //capacity是数据容量
};

 
*a指针指向顺序表的头, size是有效数据的个数,capacity是数据容量

 在这里数据我都定义成了int,但要是后面需要修改类型很麻烦,所以在前面我先重定义一下,方便后面修改:

typedef int SeqDateType;

 数据结构在项目中一般是用来存储管理数据的,对内存中管理数据的结构增删查改的接口。

这里面的接口一般有这么4个:

typedef int SeqDateType;
typedef struct SeqList
{
	SeqDateType *a;        //指针指向顺序表的头
	int size;      //size是有效数据的个数
	int capacity;  //capacity是数据容量
}SEQ;

void SeqListInit(SEQ seq);
void SeqListDestory(SEQ seq);
void SeqListPushBack(SEQ seq, SeqDateType x);
void SeqListPushFront(SEQ seq, SeqDateType x);
void SeqListPopBack(SEQ seq);
void SeqListPopFront(SEQ seq);

为了方便,我将结构体类型重定义为SEQ,下面4个接口分别是头插(PushFront)尾插(PushBack),头删(PopFront)尾删(PopBack)。

数据结构里面还有2个常用的接口:初始化(Init)和销毁(Destory),因为顺序表是动态内存开辟的空间,所以用完了需要销毁,否则会内存泄漏。

初始化(调用函数完成,不要在其他地方操作):

注意这里要传结构体变量的地址,将seq改成*pq, 将变量的地址传给Init 。

//SeqList.h 
void SeqListInit(SEQ *pq);
void SeqListDestory(SEQ* pq);
void SeqListPushBack(SEQ* pq, SeqDateType x);
void SeqListPushFront(SEQ* pq, SeqDateType x);
void SeqListPopBack(SEQ* pq);
void SeqListPopFront(SEQ* pq);

//test.c
void SeqListTest1()
{
	struct SeqList s;
	SeqListInit(&s);
	SeqListDestory(&s);
}
int main()
{
	SeqListTest1();
	return 0;
}

//SeqList.c
void SeqListInit(SEQ* pq)
{
	assert(pq);
	pq->a = NULL;
	pq->size = pq->capacity = 0;
}

注意这里用了一个断言函数 assert,防止pq为空指针。

初始化后进行增删查改等操作,完了要销毁,将指针再次置空,空间置0就行了。

void SeqListDestory(SEQ *pq)
{
	assert(pq);
	free(pq->a);
	pq->a = NULL;
	pq->size = pq->capacity = 0;
}

接着讲一下头尾增加数据的接口如何设计。

尾插:

在尾部插数据只需要把数据放在下标为size的位置(下标为size实际就是第size+1个数),然后size++就行了。要注意插入的时候size=capacity的情况,需要扩容。

代码如下:

//SeqList.h
void SeqListPushBack(SEQ* pq, SeqDateType x);
void SeqListPrint(SEQ* pq);

//SeqList.c
void SeqListPushBack(SEQ *pq, SeqDateType x)
{
	assert(pq);
	if (pq->size == pq->capacity)
	{
		//扩容
		int newcapacity = pq->capacity * 2 == 0 ? 4: pq->capacity * 2;
		SeqDateType* newA = realloc(pq->a, sizeof(SeqDateType) * newcapacity);
		if (newA == NULL)
		{
			printf("realloc fail\n");
			exit(-1);
		}
		pq->a = newA;
		pq->capacity = newcapacity;
	}
	pq->a[pq->size] = x;
	pq->size++;
}

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


//test.c
void SeqListTest1()
{
	struct SeqList s;
	SeqListInit(&s);
	SeqListPushBack(&s, 1);
	SeqListPushBack(&s, 2);
	SeqListPushBack(&s, 3);
	SeqListPushBack(&s, 4);
	SeqListPushBack(&s, 5);
	SeqListPrint(&s);
	SeqListDestory(&s);
}
int main()
{
	SeqListTest1();
	return 0;
}

头插:

头插和尾插一样,都要先考虑扩容的问题,因此我们可以把扩容单独做成一个接口分离出来。

头插上面说了需要把所有数据都往后挪动,具体实现就是从后往前将前面一位的值赋给后面一位。

用end指向末尾,每挪动一次,end--往前走一位,直到end<0。

代码如下:

//test.c
//尾插1,2,3,4,5   头插 0 ,0 ,0, 0
void SeqListTest1()
{
	struct SeqList s;
	SeqListInit(&s);
	SeqListPushBack(&s, 1);
	SeqListPushBack(&s, 2);
	SeqListPushBack(&s, 3);
	SeqListPushBack(&s, 4);
	SeqListPushBack(&s, 5);
	SeqListPushFront(&s, 0);
	SeqListPushFront(&s, 0);
	SeqListPushFront(&s, 0);
	SeqListPushFront(&s, 0);

	SeqListPrint(&s);
	SeqListDestory(&s);
}
int main()
{
	SeqListTest1();
	return 0;
}

//SeqList.c
void SeqCheckCapacity(SEQ* pq)
{
	if (pq->size == pq->capacity)
	{
		//扩容
		int newcapacity = pq->capacity * 2 == 0 ? 4 : pq->capacity * 2;
		SeqDateType* newA = realloc(pq->a, sizeof(SeqDateType) * newcapacity);
		if (newA == NULL)
		{
			printf("realloc fail\n");
			exit(-1);
		}
		pq->a = newA;
		pq->capacity = newcapacity;
	}
}
void SeqListPushFront(SEQ *pq, SeqDateType x)
{
	assert(pq);
	SeqCheckCapacity(pq);
	int end = pq->size - 1;
	while (end >= 0)
	{
		pq->a[end+1] = pq->a[end];
		end--;
	}
	pq->a[0] = x;
	pq->size++;
}

增加数据会了,删除数据就很简单了。

尾删就直接将有效数据减1就行了。

代码如下:

void SeqListPopBack(SEQ *pq)
{
	assert(pq);
	assert(pq->size > 0);
	--pq->size;
}

头删的话,也是要挪动数据,和上面相反,往前移把第一位挪出去就行了。

代码如下:

void SeqListPopFront(SEQ *pq)
{
	assert(pq);
	assert(pq->size > 0);
	int begin = 0;
	while (begin<pq->size)
	{
		pq->a[begin] = pq->a[begin+1];
		begin++;
	}
	pq->size--;
}

顺序表相对来说比较简单,后面的结构会越来越复杂,大家加油把!!!

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
比较基本的功能 #include <stdio.h> #include <stdlib.h> #include <ctype.h> typedef struct node{ char d; struct node *next; } node; node *findalpha(node *sor) /*提取字母*/ { node *nea; if (sor==NULL) { return NULL; } else if (isalpha(sor->d)) { nea=malloc(sizeof(node)); nea->d=sor->d; nea->next=findalpha(sor->next); } else return findalpha(sor->next); return nea; } node *finddigit(node *sor) /*提取数字*/ { node *nea; if (sor==NULL) { return NULL; } else if (isdigit(sor->d)) { nea=malloc(sizeof(node)); nea->d=sor->d; nea->next=finddigit(sor->next); } else return finddigit(sor->next); return nea; } node *findother(node *sor) /*提取其它字符*/ { node *nea; if (sor==NULL) { return NULL; } else if (!isdigit(sor->d)&&!isalpha(sor->d)) { nea=malloc(sizeof(node)); nea->d=sor->d; nea->next=findother(sor->next); } else return findother(sor->next); return nea; } int main(int argc, char* argv[]) { node *a=NULL,*b,*st,*alpha,*digit,*other; char c; while ((c=getchar())!='\n') /*读取字符并建*/ { if (a==NULL) { a=malloc(sizeof(node)); st=a; a->d=c; } else{ b=malloc(sizeof(node)); b->d=c; a->next=b; a=b; } } a->next =NULL; a=st; puts("\nAll:"); while (a!=NULL) /*输出所有字符*/ { printf("%c",a->d); a=a->next ; } alpha=findalpha(st); digit=finddigit(st); other=findother(st); puts("\n\nLetter:"); while (alpha!=NULL) { printf("%c",alpha->d); alpha=alpha->next ; } puts("\n\nDigit:"); while (digit!=NULL) { printf("%c",digit->d); digit=digit->next ; } puts("\n\nOther:") ; while (other!=NULL) { printf("%c",other->d); other=other->next ; } return 0; }

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值