数据结构——线性表

1.数据结构的三要素

对任何一个数据结构我们都应该思考这三要素具体指的是什么,以便进行相关操作,例如对于顺序表来说,它是一个“完整的数据结构”,逻辑结构是线性结构、存储结构是顺序存储以及施加在其上的运算,而如果单单说栈,只能表示其逻辑结构,至于存储结构不确定,可用顺序存储或者链式存储。
在这里插入图片描述

2.线性表的定义及存储结构

2.1何为线性表?

定义:线性表是具有相同数据类型的n(n>=0)个数据元素的有限序列 ,n为表长,n=0时为空表。
注意:线性表只是一种逻辑结构,表示元素一对一之间的关系,顺序表和链表指的是存储结构,二者属于不同层面的概念,不要混淆。

2.2线性表的两类存储表示

2.2.1顺序存储表示

线性表顺序存储表示又称顺序表,它用一组地址连续的存储单元依次存储线性表中的数据元素,使得逻辑相邻的两个元素在物理位置上也相邻,由于各元素连续存储在数组中,所以属于随机存取、顺序存储类型,存储密度较高(每个结点只存储数据元素),插入删除时需要移动大量元素

#define maxsize 100
//静态分配一维数组的顺序表,数组大小和空间已固定,一旦空间占满有着程序崩溃危险
typedef struct
{
	ElemType data[maxsize];
	int length;
}sqlist;

//动态分配一维数组的顺序表,用的时候自由分配数组大小,用完了可以重新分配更大的空间替换原空间
typedef struct
{
	ElemType *data;
	int length, maxsize;//length为当前个数;maxsize为最大容量(一般不用管)
}sqlist;

2.2.2链式存储表示

线性表链式存储表示又称链表,用一个个结点将各数据元素相连,不要求逻辑上相邻的元素在物理位置上也相邻,存储单元不连续,由于每个结点中有指针域,因此在进行插入和删除时只需修改指针域即可,无需移动大量元素,当然缺点就是失去了随机存取的特性,属于顺序存取,随机存储类型

3.顺序表

3.1顺序表操作汇总

3.1.1初始化顺序表

void initlist(sqList *l)//初始化顺序表,形参也可以写sqList &l,但需要在函数声明处参数改为sqList &
{
	l->length = 0;
	/*
	l->data = (int *)malloc(sizeof(int)*len);//动态分配一段连续空间,并将此段空间首地址给data,好处在于用多少分配多少空间,不用一上来就分配个最大空间
	l.length = len;
	*/
	printf("初始化成功!\n");
}

3.1.2创建顺序表

void create(sqList *l)//创建顺序表,让用户对顺序表各个元素赋值
{
	int i = 0, leng = 0;
	printf("请输入顺序表表长:\n");
	scanf("%d", &leng);
	l->length = leng;
	printf("依次输入这%d个元素:\n", leng);
	for (i; i < leng; i++)
	{
		scanf("%d", &l->data[i]);
	}
	printf("顺序表创建完成!\n");
}

3.1.3求表长

int length(sqList l)//返回当前顺序表表长
{
	return l.length;
}

3.1.4按值查找操作

int LocateElem(sqList l, int e)//查找顺序表中是否存在e值,存在返回其下标,否则返回-1
{
	int i = 0;
	for (i; i < l.length; i++)
	{
		if (l.data[i] == e)
		{
			return i+1;
		}
	}
	return -1;
}
//平均时间复杂度:O(n)

3.1.5按位查找操作

int GetElem(sqList l, int i)//返回顺序表第i位置上的元素,注意:i此时非下标
{
	int j;
	if (i<1 || i>l.length)//判断位置是否合法
	{
		printf("位置有误!!\n");
		return -1;
	}
	for ( j = 0; j != i - 1 && j < l.length; j++);
	return l.data[j];
}
//平均时间复杂度:O(1)

3.1.6删除操作

void ListDelete(sqList *l, int i, int &e)//删除第i个位置上的元素并用e存储被删除元素
{
	if (i<1 || i>l->length)//判断位置是否合法
	{
		printf("位置有误!!\n");
		return;
	}
	e = l->data[i - 1];
	for (int j = i; j < l->length; j++)//被删除元素后的所有元素依次向前移动
	{
		l->data[j - 1] = l->data[j];
	}
	l->length--;
	printf("删除成功!\n");
}
//平均时间复杂度:O(n)

3.1.7插入操作

void ListInsert(sqList *l, int i, int e)//往顺序表第i个位置插入元素e
{
	if (i<1 || i>l->length + 1)//非法位置
	{
		printf("插入位置有误!!\n");
		return;
	}
	if (l->length == maxsize)//顺序表已到最大长度,无法插入
	{
		printf("顺序表已到最大长度,无法插入\n");
		return;
	}
	for (int j = l->length-1; j >=i-1; j--)
	{
		l->data[j + 1] = l->data[j];
	}
	l->length++;
	l->data[i - 1] = e;
	printf("插入成功!\n");
}
//平均时间复杂度:O(n)

3.1.8遍历操作

void PrintList(sqList l)//遍历顺序表
{
	printf("当前顺序表为:\n");
	for (int i = 0; i < l.length; i++)
	{
		printf("%d ", l.data[i]);
	}
	putchar('\n');
}

3.1.9判空操作

bool Empty(sqList l)//判断是否为空表
{
	if (l.length == 0)
		return true;
	else
		return false;
}

3.1.10销毁操作

void DestoryList(sqList *l)//销毁顺序表,针对于顺序表结构中一维数组是动态分配的情况
{
	free(l->data);
}

主函数

#include<stdio.h>
#include<stdlib.h>
#define maxsize 50
typedef struct
{
	int length;//顺序表实际长度
	int data[maxsize];//顺序表的元素
}sqList;
int main()
{
	sqList L;//定义一个顺序表,已为其分配了空间
	int e, i, service, flag = 1, len = 0;
	void initlist(sqList *);
	void create(sqList *);
	int length(sqList );
	int LocateElem(sqList ,int);
	int GetElem(sqList,int);
	void ListDelete(sqList *,int,int&);
	void ListInsert(sqList *,int,int);
	void PrintList(sqList);
	bool Empty(sqList);
	void DestoryList(sqList *);
	while (flag)
	{
		printf("请输入操作序号:\n");
		printf("\t1.初始化顺序表\t\t2.创建顺序表\n");
		printf("\t3.求表长\t\t4.按值查找元素\n");
		printf("\t5.获取某个位置上的元素\t\t6.删除某位置元素\n");
		printf("\t7.在某位置上插入某元素\t\t8.输出当前顺序表\n");
		printf("\t9.当前顺序表是否为空\t\t10.销毁顺序表\n");
		scanf("%d", &service);
		switch (service)
		{
		case 1:
			initlist(&L);
			break;
		case 2:
			create(&L);
			break;
		case 3:
			len = length(L);
			printf("表长为%d\n", len);
			break;
		case 4:
			printf("输入你要查找的值:\n");
			scanf("%d", &e);
			if (LocateElem(L, e) != -1)
				printf("此值是第%d个元素\n", LocateElem(L, e));
			else
				printf("没有这个值\n");
			break;
		case 5:
			printf("你要查找第几个位置上的值?\n");
			scanf("%d", &i);
			if (GetElem(L, i) != -1)
			{
				printf("%d位置上的值为%d\n", i, GetElem(L, i));
			}
			break;
		case 6:
			printf("你要删除第几个位置上的元素?\n");
			scanf("%d", &i);
			ListDelete(&L, i, e);
			break;
		case 7:
			printf("请依次输入插入位置及值:\n");
			scanf("%d%d", &i, &e);
			ListInsert(&L, i, e);
			break;
		case 8:
			PrintList(L);
			break;
		case 9:
			if (Empty(L))
				printf("当前为空!\n");
			else
				printf("当前不为空!\n");
			break;
		case 10:
			DestoryList(&L);
			break;
		default:
			break;
		}
		printf("继续操作输入1否则输入0:\n");
		scanf("%d", &flag);
	}
	return 0;
}

运行结果

请输入操作序号:
        1.初始化顺序表          2.创建顺序表
        3.求表长                4.按值查找元素
        5.获取某个位置上的元素          6.删除某位置元素
        7.在某位置上插入某元素          8.输出当前顺序表
        9.当前顺序表是否为空            10.销毁顺序表

3.2练习

3.2.1逆置问题

问题描述:将一个数组中的各元素按照用户的各种需求进行逆置,如以下三种情况

  1. 将一长度为n的数组的前端k(k<n)个元素逆序后移到数组后端,要求原数组中数据不丢失,其余位置元素无关紧要,如13579变为57931。
  2. 将一长度为n的数组的前端k(k<n)个元素保持原序移到数组后端,要求原数组中数据不丢失,其余位置元素无关紧要,如13579变为57913。
  3. 将数组中元素(X0,X1,…,Xn-1),经过移动后变为(Xp,Xp+1,…,Xn-1,X0,X1,…,Xp-1),即循环左移p(0<p<n)个位置,如13579变为57913。

思路:其实上述三类问题属于同一类问题,可以用一个通用函数(reverse)依次解决,只需改变此函数的形参即可。

  1. 问题1解法:直接逆置整个数据即可。
  2. 问题2解法:先逆置前k个元素,在逆置整个数组即可。
  3. 问题3解法:先将0~p-1位置元素逆置,在逆置p—n-1位置元素,最后逆置整个数组即可
void reverse(int *a, int left, int right)//形参含义:将数组a从下标left到right的k个元素逆置
{
	int temp;
	for (int i = left, j = right; i<j; ++i, --j)
	{
		temp = a[i];
		a[i] = a[j];
		a[j] = temp;
	}
}

问题三实现

#include<stdio.h>
int main()
{
	int a[8] = { 1,3,65,4,2,15,5,98 };
	void reverse(int *, int, int);
	reverse(a, 0, 4);
	reverse(a, 5, 7);
	reverse(a, 0, 7);
	for (int i = 0; i < 8; i++)
		printf("%d ", a[i]);
	return 0;
}
void reverse(int *a, int left, int right)//形参含义:将数组a从下标left到right的k个元素逆置
{
	int temp;
	for (int i = left, j = right; i<j; ++i, --j)
	{
		temp = a[i];
		a[i] = a[j];
		a[j] = temp;
	}
}

运行结果
15 5 98 1 3 65 4 2

4.链表

4.1何为单链表

通过一组任意的存储单元来存储线性表中的数据元素,为了建立元素间线性关系,对每个链表结点定义两个域——指针域和数据域,数据域用来存放数据,指针域用来指向其后继结点,由于单链表的元素随机离散的分布在存储空间,所以是非随机存取的存储结构,即不能直接找到某特定顶点,需要从表头遍历查找 ,空表条件是头指针l->next等于NULL,结点结构如下

typedef struct node
{
	int data;
	struct node *next;
}lnode,*linklist;//linklist p等价于lnode *p

4.2单链表操作

4.2.1头插法建立单链表

void headcreate(linklist &L)//头插法建立单链表
{
	int x;
	lnode *newnode = NULL;//接收新结点
	L = (linklist)malloc(sizeof(lnode));//分配头结点空间并让L指向它,与下面一行统称为初始化操作
	L->data = 999;//令头结点数据域为999
	L->next = NULL;
	printf("请依次输入你想要创建的结点的值(9999代表结束):\n");
	scanf("%d", &x);
	while (x != 9999)
	{
		newnode = (linklist)malloc(sizeof(lnode));
		newnode->data = x;
		newnode->next = L->next;
		L->next = newnode;
		scanf("%d", &x);
	}
	printf("创建完毕!!\n");
}
//平均时间复杂度:O(n)

4.2.2尾插法建立单链表

void trailcreate(linklist &L)//尾插法建立单链表
{
	int x;
	lnode *newnode = NULL;
	L = (linklist)malloc(sizeof(lnode));//分配头结点空间并让L指向它,与下面一行统称为初始化操作
	lnode *r = L;//指向链表尾
	L->data = 999;
	L->next = NULL;
	printf("请依次输入结点值(9999代表结束):\n");
	scanf("%d", &x);
	while (x!=9999)
	{
		newnode = (linklist)malloc(sizeof(lnode));
		newnode->data = x;
		r->next = newnode;
		newnode->next = NULL;
		r = newnode;
		scanf("%d", &x);
	}
	printf("创建完毕!!\n");
}

4.2.3返回某位置i的元素的指针

lnode *GetElem(linklist L, int i)//返回指向链表第i位置上的指针
{
	int count = 1;//记录当前遍历的顶点是第几个顶点
	linklist p = L->next;
	if (i == 0)//记住:头结点位置可以当做0位置,可以返回头结点的位置的话这样在后面如果插入函数中要在1位置插入元素就可以用此函数来找前驱,但其实若不是为了插入和删除服务的话,其实0位置上并没什么意义
		return L;
	if (i < 1)
		return NULL;//位置过小导致异常
	while (p&&count<i)
	{
		p=p->next;
		count++;
	}
	return p;
}
//平均时间复杂度:O(n)

4.2.4返回某元素e的指针

lnode *LocateElem(linklist L, int e)//查找值为e的元素,若存在返回其指针
{
	lnode *p = L->next;
	while (p&&p->data!=e)
	    p=p->next;
	return p;
}
//平均时间复杂度:O(n)

4.2.5插入元素

void Insert(linklist L, int i, int e)//在链表第i位置上插入元素e,此函数需要留心,B站上王老师的那种方法也要理解
{
	lnode *p = L->next;
	lnode *pre = L;
	lnode *newnode = NULL;
	newnode = (linklist)malloc(sizeof(lnode));
	newnode->next = NULL;
	int count = 1;
	while (p&&count < i)
	{
		pre = p;
		p = p->next;
		count++;
	}
	if (count == i)
	{
		newnode->data = e;
		newnode->next = pre->next;
		pre->next = newnode;
		printf("插入完毕!\n");
	}
	else
		printf("插入位置非法!\n");
}

4.2.6删除元素

void DeleteElem(linklist L, int i, int &e)//删除第i个位置上的结点,并将其数据存进e,此函数需要留心
{
	lnode *GetElem(linklist , int );//利用前面编好的函数
	linklist q = NULL;
	linklist pre = GetElem(L, i - 1);//返回第i个结点的前驱结点指针
	if (pre != NULL)
	{
		q = pre->next;//q存放要删除的结点
		e = q->data;
		pre->next = q->next;
		free(q);
		printf("第%d位置上的元素%d已成功删除!\n", i, e);
	}
	else
	{
		printf("位置有误!\n");
	}
}

4.2.7求表长

int Getlength(linklist L)//获取表长
{
	linklist p = L->next;
	int count = 1;
	while (p->next != NULL)
	{
		count++;
		p = p->next;
	}
	return count;
}

4.2.8遍历

void Print(linklist L)//遍历函数
{
	linklist  p = L->next;
	while (p)
	{
		printf("%d ", p->data);
		p = p->next;
	}
	printf("遍历完毕!\n");
}

主函数

#include<stdio.h>
#include<stdlib.h>
typedef struct node
{
	int data;
	struct node *next;
}lnode,*linklist;//linklist p等价于lnode *p
int main()
{
	int i, e, num, flag = 1;
	linklist p = NULL;//p作为中间变量使用,若不在此处定义,改为在使用的时候在定义会出现“p的初始化操作由case标签跳过”的错误
	linklist l;//此时l只是一个没有指向的指针
	void headcreate(linklist &);
	void trailcreate(linklist &);
	lnode *GetElem(linklist, int);
	lnode *LocateElem(linklist, int);
	void Insert(linklist , int, int);
	void DeleteElem(linklist , int, int &);//切记:引用型变量需要在函数声明处加上&,引用的变量是什么类型就在其类型后加&
	int Getlength(linklist);
	void Print(linklist );
	while (flag)
	{
		printf("\t1.头插法建立单链表\t\t2.尾插法建立单链表\n");
		printf("\t3.获取第i个结点的指针\t\t4.获取值为e的结点的指针\n");
		printf("\t5.插入新的节点\t\t6.删除结点\n");
		printf("\t7.表长\t\t8.遍历链表\n");
		printf("\t\t\t\t9.结束\n");
		printf("请输入需要的操作序号:\n");
		scanf("%d", &num);
		switch (num)
		{
		case 1:
			headcreate(l);
			break;
		case 2:
			trailcreate(l);
			break;
		case 3:
			printf("请您输入位置:\n");
			scanf("%d", &i);
			p = GetElem(l,i);
			if (p == NULL)
				printf("位置有误!\n");
			else
				printf("第%d个位置上的值为%d\n", i, p->data);
			break;
		case 4:
			printf("请输入需要获取的值:\n");
			scanf("%d", &e);
			p=LocateElem(l, e);
			if (p)
				printf("返回值为%d的指针成功!!!\n", e);
			else
				printf("没有这个值!!!\n");
			break;
		case 5:
			printf("请输入要插入的位置和值:\n");
			scanf("%d%d", &i, &e);
			Insert(l, i, e);
			break;
		case 6:
			printf("请输入你要删除的位置:\n");
			scanf("%d", &i);
			DeleteElem(l, i, e);
			break;
		case 7:
			num = Getlength(l);
			printf("表长为%d\n", num);
			break;
		case 8:
			Print(l);
			break;
		case 9:
			exit(0);
			break;
		}
		printf("是否继续?\n");
		scanf("%d", &flag);
	}
	return 0;
}

运行结果

        1.头插法建立单链表              2.尾插法建立单链表
        3.获取第i个结点的指针           4.获取值为e的结点的指针
        5.插入新的节点          6.删除结点
        7.表长          8.遍历链表
                                9.结束
请输入需要的操作序号:

4.3练习题

4.3.1归并问题

问题描述:A和B是两个带头结点的单链表,元素递增有序,设计算法将A和B归并 成一个按元素非递减有序的链表C,C有A和B中结点组成。

思路:所谓非递减就是类似1、2、2、3类似的序列,不严格要求n项大于n-1项(可等于),其次需注意假如A={10,11,12},B={1,3,5},我们是不是可以直接判断由于A的第一个元素大于B的最后一个元素,因而直接将B的最后一个元素与A相连?答案是:不可以,原因是有归并二字的出现,标准的归并算法如下,可以看出若按先前想法则会多一步归并中没有的判断操作

void merge(linklist A, linklist B, linklist &C)
{
	lnode *p = A->next;//p和q分别指向两链表首元结点
	lnode *q = B->next;
	C = A;//截取A的头结点作为新链表C的头结点
	C->next = NULL;
	lnode *r = C;//r始终指向C的终端节点,以便插入新结点
	free(B);//B的头结点已经无用
	while (p != NULL && q != NULL)
	{
		if (p->data < q->data)
		{
			r->next = p;
			p = p->next;
			r = r->next;
		}
		else
		{
			r->next = q;
			q = q->next;
			r = r->next;
		}
	}
	//A和B总有一个先检查完,将剩下的那个链表剩余部分直接接在C的尾部即可
	if (p != NULL)
		r->next = p;
	if (q != NULL)
		r->next = q;
}

4.3何为双链表

单链表结点中只有一个指向其后继的指针,使得单链表只能从头结点依次顺序向后遍历,访问前驱结点的时间复杂度为O(n),为此出现了双链表,链表结点有两个指针———prior和next,前者指向前驱结点,后者指向后继结点,空表的条件是头指针l->next等于NULL

typedef struct DNode
{
	ElemType data;
	struct DNode *next, *prior;
}DNode, *DLinklist;

4.4双链表操作

4.4.1尾插法建立双链表

void trailcreate(DLinklist &l)
{
	int now(DLinklist);
	int i;
	DNode *r = NULL, *newnode = NULL;
	l = (DLinklist)malloc(sizeof(DNode));
	l->next = l->prior = NULL;
	r = l;
	printf("请输入各节点的值(9999代表结束):\n");
	scanf("%d", &i);
	while (i != 9999)
	{
		newnode = (DLinklist)malloc(sizeof(DNode));
		newnode->data = i;
		newnode->prior = r;
		r->next = newnode;
		newnode->next = NULL;
		r = r->next;
		scanf("%d", &i);
	}
	printf("创建完毕!\n");
	printf("链表当前结点数为:%d\n", now(l));
}

4.4.2头插法建立双链表

void headcreate(DLinklist &l)
{
	int now(DLinklist);
	int i, flag = 0;//flag用于特殊处理用户输入的第一个结点
	DLinklist newnode = NULL;
	l = (DLinklist)malloc(sizeof(DNode));
	l->next = l->prior = NULL;
	printf("请输入各个结点的值(9999代表结束):\n");
	scanf("%d", &i);
	flag = 1;
	while (i != 9999)
	{
		newnode = (DLinklist)malloc(sizeof(DNode));
		newnode->next = NULL;
		newnode->data = i;
		if (flag)//对第一个插入的结点特殊处理
		{
			newnode->next = NULL;
			l->next = newnode;
			newnode->prior = l;
			flag = 0;
		}
		else
		{
			newnode->next = l->next;
			l->next->prior = newnode;
			l->next = newnode;
			newnode->prior = l;
		}
		scanf("%d", &i);
	}
	printf("建立成功!\n");
	printf("链表当前结点数为:%d\n", now(l));
}

4.4.3按值查找结点

DNode *findnode(DLinklist l, int x)
{
	DLinklist p = l->next;
	while (p != NULL)//循环条件中建议不要写p->一类的语句,容易造成野指针
	{
		if (p->data != x)
			p = p->next;
	}
	return p;
}

4.4.4插入结点☆

void insert(DLinklist l, int i, int e)
{
	int now(DLinklist);
	DLinklist p = l->next;
	DLinklist trail = NULL;
	DLinklist newnode = (DLinklist)malloc(sizeof(DNode));
	newnode->data = e;
	newnode->next = newnode->prior = NULL;
	int count = 1;
	if (i <= 0)
	{
		printf("位置非法!\n");
		return;
	}
	while (count < i&&p)
	{
		p = p->next;
		count++;
		if (p)//以下2行都是为了用户如果要求在n+1位置插入元素而做的特殊处理,原因是此链表插入元素的原理是找到第i个元素后进行操作,而i+1元素自然找不到
		{
			if (p->next == NULL && count != i)
				trail = p;
		}
	}
	if (count == i&&trail!=NULL)
	{
		trail->next = newnode;
		newnode->prior = trail;
		newnode->next = NULL;
	}
	else if(count == i)
	{
		newnode->next = p;
		p->prior->next = newnode;
		newnode->prior = p->prior;
		p->prior = newnode;
	}
	else
	{
		printf("位置非法!\n");
	}
	printf("链表当前结点数为:%d\n", now(l));
}

代码解释:插入的合法位置为[1,n+1],由于双链表具有可以快速找到结点前驱的特性,因此对于插入操作,用户想在某个位置插入只需找到此位置结点即可,无需向单链表一样找到i-1个结点,但是由于双链表的插入操作是要涉及对当前结点的前驱进行操作,但是由于通过第n+1结点无法找到其前驱,所以要对其进行特殊处理,具体做法是引入trail指针保存尾结点.

4.4.5删除结点(有点错误)☆

void deleteelem(DLinklist l, int i)
{
	int now(DLinklist);
	DLinklist p = l->next;
	DLinklist s = NULL;
	DLinklist trail = NULL;
	int  count = 1;
	if (i <= 0)
	{
		printf("位置非法!\n");
		return;
	}
	while (i > count && p)
	{
		count++;
		p = p->next;
		if (p)
		{
			if (p->next == NULL)
				trail = p;
		}
	}
	if (count == i && trail != NULL&&p)//特殊处理i等于n时,p!=NULL主要作用是为了i排除大于n的情况,因为trail是从count==n时开始被赋值的,后面就一直有值,不加这一句的话后面会出现野指针
	{
		s = trail;
		trail->prior->next = NULL;
		printf("删除成功!\n");
	}
	else if (p&&i == count)//处理用户输入的i为1~n-1位置时,同样也要排除i>n的情况
	{
		s = p;
		p->prior->next = s->next;
		s->next->prior = s->prior;
		free(s);
		printf("删除成功!\n");
	}
	else
		printf("位置非法!\n");
	printf("链表当前结点数为:%d\n", now(l));
}

代码解释:不同于插入操作,删除某一个结点需要对其前驱和后继都进行操作,所以同时拥有前驱和后继的属于一种情况([1-n-1]位置),但是n位置无后继所以需要特殊处理。

对于删除和插入的小结:写代码时需要注意哪种情况归为一类,哪种情况归为另一类,插入算法中,通过某结点可以找到其前驱的归为一类([1,n]位置),无法找到其前驱的归为一类进行特殊处理(n+1位置时);删除算法中可以同时找到前驱和后继的属于同一类([1,n-1]位置),无法同时找到前驱和后继的归为一类进行特殊处理(n位置)。

4.5何为单循环链表

链表尾结点的指针域不再为NULL,而是指向 头结点,从而形成一个环,相比于普通单链表,单循环链表的最大改变在于 插入和删除操作,普通单链表中我们可以通过指针是否为NULL来判断是否扫描到链表尾,依次来结束循环,但是由于单循环链表中不存在指针为NULL的情况,所以就需要设置一个标志(flag),当flag为1时表明已经扫描过链表一遍了,无需再次重复扫描,空表条件是头指针l->next等于l。

4.6单循环链表操作

4.6.1头插法建立单循环链表

void headcreate1(linklist & l)
{
	int now1(linklist l);
	int x;
	int flag = 0;//不同1
	lnode *r = NULL;//不同2:r始终指向尾结点
	lnode *newnode = NULL;//接收新结点
	l = (linklist)malloc(sizeof(lnode));//分配头结点空间并让L指向它,与下面一行统称为初始化操作
	l->data = 999;//令头结点数据域为999
	l->next = l;//不同3:
	printf("请依次输入你想要创建的结点的值(9999代表结束):\n");
	scanf("%d", &x);
	while (x != 9999)
	{
		newnode = (linklist)malloc(sizeof(lnode));
		newnode->data = x;
		newnode->next = l->next;
		l->next = newnode;
		if (!flag)//不同4:插入第一个节点后此结点永远变成尾结点了,让r指向它,最后构成循环做准备
		{
			r = newnode;
			flag = 1;
		}
		scanf("%d", &x);
	}
	if (r != NULL)//处理用户直接输入9999的情况
	{
		r->next = l;//不同:5:尾结点next域指向头结点构成循环单链表
	}
	printf("创建完毕!!\n");
	printf("当前链表剩余节点个数:%d个\n", now1(l));
}

4.6.2尾插法建立单循环链表

void trailcreate1(linklist &l)
{
	int now1(linklist l);
	int x;
	lnode *newnode = NULL;
	l = (linklist)malloc(sizeof(lnode));//分配头结点空间并让L指向它,与下面一行统称为初始化操作
	lnode *r = l;//指向链表尾
	l->data = 999;
	l->next = NULL;
	printf("请依次输入结点值(9999代表结束):\n");
	scanf("%d", &x);
	while (x != 9999)
	{
		newnode = (linklist)malloc(sizeof(lnode));
		newnode->data = x;
		r->next = newnode;
		newnode->next = NULL;
		r = newnode;
		scanf("%d", &x);
	}
	if(r!=NULL)//处理用户直接输入9999的情况
	  r->next = l;//不同点1
	printf("创建完毕!!\n");
	printf("当前链表剩余节点个数:%d个\n", now1(l));
}

4.6.3删除指定位置元素(☆)

void deleteelem1(linklist l, int i)
{
	int flag = 0;
	int now1(linklist l);
	linklist pre = l, p = l->next;
	linklist q = NULL;
	int count = 1;
	if (i <= 0)
	{
		printf("位置非法!\n");
		return;
	}
	while (i > count && !flag)//p->next!=l用来防止对链表进行了二次扫描、二次计数,正常情况应该是检查到表尾若还没有这个位置就该退出了
	{
		count++;
		pre = p;
		p = p->next;
		if (p == l)//与insert1作对比,这里用的是p是否等于l,就不会把n+1位置也当做正常情况了
			flag = 1;
	}
	if (i == count&&!flag)
	{
		q = p;
		pre->next = p->next;
		free(q);
		printf("已被删除!\n");
	}
	else 
		printf("位置非法!\n");
	printf("当前链表剩余节点个数:%d个\n", now1(l));
}

4.6.4在指定位置插入元素(☆)

void insert1(linklist l, int i, int e)
{
	int now1(linklist l);
	int flag = 0;//用来标记是否已经走完一遍链表,防止重复查找
	lnode *p = l->next;//p指向首元结点
	lnode *pre = l;//指向p的前驱结点
	lnode *newnode = NULL;
	newnode = (linklist)malloc(sizeof(lnode));
	newnode->next = NULL;
	int count = 1;
	if (i <= 0)
	{
		printf("位置非法!\n");
		return;
	}
	while (count < i&&!flag)//由于循环链表没有指针域为NULL的结点,所以以flag为0代表一遍链表还没走完
	{
		pre = p;
		if (pre == l)//当pre都指向l了就证明链表已经扫描到尾部了,后面的位置都是非法位置,这里用pre==l可以把i为n+1位置同样处理了
			flag = 1;
		p = p->next;
		count++;
	}
	if (count == i&&!flag)//必须保证没有重复查找
	{
		newnode->data = e;
		newnode->next = pre->next;
		pre->next = newnode;
		if (newnode->next == NULL)//不同点1:若是在n+1位置插入元素则需特殊处理以保持循环特性
		{
			newnode->next = l;
		}
		printf("插入完毕!\n");
	}
	else
	{
		printf("插入位置非法!\n");
	}
	printf("当前链表剩余节点个数:%d个\n", now1(l));
}

4.6.5输出

void print1(linklist l)
{
	int now1(linklist l);
	linklist p = l->next;
	while (p != l)
	{
		printf("%d ", p->data);
		p = p->next;
	}
	printf("遍历结束\n");
	printf("当前链表剩余节点个数:%d个\n", now1(l));
}

4.7何为双循环链表

顾名思义,它的基础是双链表,只需让尾结点next域指向头结点,头结点prior域指向尾结点即可,空表的条件是p->prior和p->next全为L时。

4.8双循环链表的操作

4.8.1头插法建立双循环链表

void headcreate2(Dlinklist &l)
{
	int now2(Dlinklist l);
	int i, flag = 0;//flag用于特殊处理用户输入的第一个结点
	Dlinklist newnode = NULL;
	l = (Dlinklist)malloc(sizeof(Dnode));
	l->next = l->prior = l;//不同1:链表为空条件改变
	Dlinklist r = NULL;//不同2:r始终指向尾结点
	printf("请输入各个结点的值(9999代表结束):\n");
	scanf("%d", &i);
	flag = 1;
	while (i != 9999)
	{
		newnode = (Dlinklist)malloc(sizeof(Dnode));
		newnode->next = NULL;
		newnode->data = i;
		if (flag)//对第一个插入的结点特殊处理
		{
			newnode->next = NULL;
			l->next = newnode;
			newnode->prior = l;
			r = newnode;//不同3:第一个被插入的结点永远变成尾结点,让r指向它
			flag = 0;
		}
		else
		{
			newnode->next = l->next;
			l->next->prior = newnode;
			l->next = newnode;
			newnode->prior = l;
		}
		scanf("%d", &i);
	}
	//构成循环双链表
	if (r != NULL)//处理用户直接输入9999的情况
	{
		r->next = l;//不同4
		l->prior = r;//不同5
	}
	printf("建立成功!\n");
	printf("当前链表剩余节点个数:%d个\n", now2(l));
}

4.8.2尾插法建立双循环链表

void trailcreate2(Dlinklist &l)
{
	int now2(Dlinklist l);
	int i;
	Dnode *r = NULL, *newnode = NULL;
	l = (Dlinklist)malloc(sizeof(Dnode));
	l->next = l->prior = NULL;
	r = l;
	printf("请输入各节点的值(9999代表结束):\n");
	scanf("%d", &i);
	while (i != 9999)
	{
		newnode = (Dlinklist)malloc(sizeof(Dnode));
		newnode->data = i;
		newnode->prior = r;
		r->next = newnode;
		newnode->next = NULL;
		r = r->next;
		scanf("%d", &i);
	}
	if (r != NULL)//不同点1;处理用户直接输入9999的情况
	{
		r->next = l;
		l->prior = r;
	}
	printf("创建完毕!\n");
	printf("当前链表剩余节点个数:%d个\n", now2(l));
}

4.8.3删除指定位置元素(☆)

void deleteelem2(Dlinklist l, int i)
{
	int flag = 0;
	int now2(Dlinklist l);
	Dlinklist p = l->next;
	Dlinklist s = NULL;
	int  count = 1;
	if (i <= 0)
	{
		printf("非法位置!\n");
		return;
	}
	while (i > count&&!flag)
	{
		count++;
		p = p->next;
		if (p == l)//p==l证明此后的位置都是非法位置
			flag = 1;
	}
	if (count == i&&!flag)//删除位置也不能为n+1
	{
		s = p;
		p->prior->next = p->next;
		p->next->prior = p->prior;
	}
	else
		printf("位置非法!\n");
	printf("当前链表剩余节点个数:%d个\n", now2(l));
}

4.8.4在指定位置插入元素(☆)

void insert2(Dlinklist l, int i, int e)
{
	int flag = 0;
	int now2(Dlinklist l);
	Dlinklist p = l->next;
	Dlinklist newnode = (Dlinklist)malloc(sizeof(Dnode));
	newnode->next = newnode->prior = NULL;
	newnode->data = e;
	int count = 1;
	if (i <= 0)
	{
		printf("位置非法!\n");
		return;
	}
	while (count <i && !flag)
	{
		count++;
		p = p->next;
		if (p == l)//这一步结束后刚好可以用来处理i等于n+1的情况,大于n+1的情况也不可能出现了
			flag = 1;
	}
	if (count==i)
	{
		newnode->next = p;
		p->prior->next = newnode;
		newnode->prior = p->prior;
		p->prior = newnode;
	}
	else
	{
		printf("位置非法!\n");
	}
	printf("当前链表剩余节点个数:%d个\n", now2(l));
}

4.8.5输出

void print2(Dlinklist l)
{
	int now2(Dlinklist l);
	Dlinklist p = l->next;
	while (p != l)
	{
		printf("%d ", p->data);
		p = p->next;
	}
	printf("遍历结束\n");
	printf("当前链表剩余节点个数:%d个\n", now2(l));
}

主函数

#include<stdio.h>
#include<stdlib.h>
typedef struct node
{
	int data;
	struct node *next;
}lnode,*linklist;
typedef struct Dnode
{
	int data;
	struct Dnode *next;
	struct Dnode *prior;
}Dnode, *Dlinklist;
int main()
{
	int flag = 1, i;
	int j, e;
	linklist p = NULL;
	Dlinklist q = NULL;
	void headcreate1(linklist &);
	void headcreate2(Dlinklist &);
	void trailcreate1(linklist &);
	void trailcreate2(Dlinklist &);
	void insert1(linklist, int, int);
	void insert2(Dlinklist, int, int);
	void deleteelem1(linklist, int);
	void deleteelem2(Dlinklist, int);
	bool Empty1(linklist);
	bool Empty2(Dlinklist);
	void print1(linklist);
	void print2(Dlinklist);
	while (flag)
	{
		printf("\t1.头插法建立单循环链表\t\t2.头插法建立双循环链表\n");
		printf("\t3.尾插法建立单循环链表\t\t4.尾插法建立双循环链表\n");
		printf("\t5.往单循环链表中插入元素\t6.往双循环链表中插入元素\n");
		printf("\t7.删除单循环链表中某元素\t8.删除双循环链表中某元素\n");
		printf("\t9.判断单循环链表是否为空\t10.判断双循环链表是否为空\n");
		printf("\t11.输出单循环链表\t\t12.输出双循环链表\n");
		printf("\t\t\t\t13.退出程序\n");
		printf("请输入操作序号:\n");
		scanf("%d", &i);
		switch (i)
		{
		case 1:
			headcreate1(p);
			break;
		case 2:
			headcreate2(q);
			break;
		case 3:
			trailcreate1(p);
			break;
		case 4:
			trailcreate2(q);
			break;
		case 5:
			printf("请输入插入元素的位置及值:\n");
			scanf("%d%d", &j, &e);
			insert1(p, j, e);
			break;
		case 6:
			printf("请输入插入元素的位置及值:\n");
			scanf("%d%d", &j, &e);
			insert2(q, j, e);
			break;
		case 7:
			printf("请输入删除位置:\n");
			scanf("%d", &j);
			deleteelem1(p, j);
			break;
		case 8:
			printf("请输入删除位置:\n");
			scanf("%d", &j);
			deleteelem2(q, j);
			break;
		case 9:
			Empty1(p) == false ? printf("当前是非空表\n") : printf("当前是空表\n");
			break;
		case 10:
			Empty2(q) == false ? printf("当前是非空表\n") : printf("当前是空表\n");
			break;
		case 11:
			print1(p);
			break;
		case 12:
			print2(q);
			break;
		case 13:
			exit(0);
			break;
		}
		printf("是否继续?\n");
		scanf("%d", &flag);
	}
}

运行结果

        1.头插法建立单循环链表          2.头插法建立双循环链表
        3.尾插法建立单循环链表          4.尾插法建立双循环链表
        5.往单循环链表中插入元素        6.往双循环链表中插入元素
        7.删除单循环链表中某元素        8.删除双循环链表中某元素
        9.判断单循环链表是否为空        10.判断双循环链表是否为空
        11.输出单循环链表               12.输出双循环链表
                                13.退出程序
请输入操作序号:

4.9何为静态链表

静态数组借助链表来描述线性表的链式存储结构,结点还是数据域和指针域,区别于普通链表的是,静态链表结点中指针域不在是一个指针变量,而是一个int型变量,它指向后继结点在数组中的下标,规定头结点存在数组a[0]位置,结点的数据域为空,指针域自然指向首元结点在数组中的下标,结点结构如下:

#define maxsize 50
typedef struct
{
	int data;
	int next;
}SLinkList[maxsize];//主函数中SLinkList p等价于SLinkList p[maxsize]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值