1、概念
线性表:是具有相同属性的数据元素的一个有限序列
线性表的逻辑结构示意图:
线性表的存储结构分为:顺序、链接、索引和散列等,常见的是顺序存储和链接存储(单链表和双链表)。
2、线性表的顺序存储结构
线性表的顺序存储结构:把线性表中的所有元素按照其逻辑顺序依次存储到计算机存储器中从指定存储位置开始的一块连续的存储空间中,可直接用数组表示。
线性表的顺序存储结构示意图:
下面是通过一个实例实现顺序存储结构的操作,实例中包含16种操作实现,在其他程序中可直接套用:
// 线性表的顺序存储结构的操作实现
#include<stdio.h>
#include<stdlib.h>
typedef int ElemType; //定义元素类型
struct List //定义单链表结点类型
{
ElemType *list;//存储空间基址
int size; //当前长度
int MaxSize; //当前分配的存储容量,即存储线性表的最大长度
};
//1、初始化线性表L,即进行动态存储空间分配并置L为一个空表
void InitList(struct List *L, int ms)
{
if (ms < 0) //检查ms是否有效
{
printf("ms值非法!\n");
exit(1);
}
L->MaxSize = ms; //置线性表初始存储容量为ms
L->list = (ElemType *)malloc(ms*sizeof(ElemType)); //动态存储空间分配
if (!L->list)
{
printf("动态存储分配失败!\n");
exit(1);
}
L->size = 0; //初始置线性表为空
}
//2、清除线性表L中的所有元素,释放动态存储空间,使之成为一个空表
void ClearList(struct List *L)
{
if (L->list != NULL)
{
free(L->list);
L->list = 0;
L->size = L->MaxSize = 0;
}
}
//3、返回线性表L的长度,若L为空则返回0
int SizeList(struct List *L)
{
return L->size;
}
//4、判断线性表L是否为空,若为空则返回1,否则返回0
int EmptyList(struct List *L)
{
if (L->size == 0)
return 1;
else
return 0;
}
//5、返回线性表L中第pos个元素的值,若pos超出范围,则停止程序运行
ElemType GetElem(struct List *L, int pos)
{
if (pos < 1 || pos > L->size)
{
printf("元素序号越界!\n");
exit(1);
}
return L->list[pos - 1];
}
//6、顺序扫描(即遍历)输出线性表L中的每个元素
void TraverseList(struct List *L)
{
int i;
for (i = 0; i < L->size; i++)
printf("%d,", L->list[i]);
printf("\n");
}
//7、从线性表L中查找值与x相等的元素(第一个),若查找成功则返回其位置(下标),否则返回-1
int FindList(struct List *L, ElemType x)
{
int i;
for (i = 0; i < L->size; i++) //此处类型ElemType为整型,当为字符串类型(char *)时,
if (L->list[i] == x) //if语句应改为: if (strcmp(L->list[i], x) == 0)
return i;
return -1;
}
//8、把线性表L中第pos个元素的值修改为x的值,若修改成功返回1,否则返回0
int UpdatePosList(struct List *L, int pos, ElemType x)
{
if (pos < 1 || pos > L->size) //若pos越界则修改失败
return 0;
L->list[pos - 1] = x;
return 1;
}
//9、向线性表L的表头插入元素x
//此时需要考虑到线性表存储空间已满的情况,则需要重新分配更大的动态存储空间,具体实现如下:
void againMalloc(struct List *L)
{
ElemType *p = realloc(L->list, 2*L->MaxSize*sizeof(ElemType));//此处重新分配的空间为原来的2倍
if (!p) //重新分配失败
{
printf("存储空间用完!\n");
exit(1);
}
L->list = p; //使list指向新线性表空间
L->MaxSize = 2 * L->MaxSize;
printf("存储空间已扩大为当前的2倍!\n");//输出提示已扩充空间
}
void InserFirstList(struct List *L, ElemType x) //表头插入元素
{
int i;
if (L->size == L->MaxSize) //存储空间已满
againMalloc(L); //重新分配更大空间
for (i = L->size - 1; i >= 0; i--)
L->list[i + 1] = L->list[i];
L->list[0] = x;
L->size++;
}
//10、向线性表L的表尾插入元素x
void InsertLastList(struct List *L, ElemType x)
{
if (L->size == L->MaxSize)
againMalloc(L);
L->list[L->size] = x;
L->size++;
}
//11、向线性表L中第pos个元素位置插入元素x,若插入成功返回1,否则返回0
int InsertPosList(struct List *L, int pos, ElemType x)
{
int i;
if (pos < 1 || pos > L->size + 1) //pos的合法位置是第一位到最后一位的后一位之间
return 0;
if (L->size == L-> MaxSize)
againMalloc(L);
for (i = L->size - 1; i >= pos - 1; i--)
L->list[i + 1] = L->list[i];
L->list[pos - 1] = x;
L->size++;
return 1;
}
//12、向有序(递增)线性表L中插入元素x,使得插入后仍然有序
void InsertOrderList(struct List *L, ElemType x)
{
int i, j;
if (L->size == L->MaxSize)
againMalloc(L);
for (i = 0; i < L->size; i++)
if (x < L->list[i])
break; //此时i的值即为要插入的位置。若x比所有元素都大,则i自增后的值为size,下面的for循环不执行。
for (j = L->size - 1; j >= i; j--)
L->list[j + 1] = L->list[j];
L->list[i] = x;
L->size++;
}
//13、从线性表L中删除表头元素并返回它,若删除失败则停止程序运行
ElemType DeleteFirstList(struct List *L)
{
ElemType temp; //临时变量,用于存储表头元素
int i;
if (L->size == 0)
{
printf("线性表为空,不能删除!\n");
exit(1);
}
temp = L->list[0];
for (i = 1; i < L->size; i++)
L->list[i - 1] = L->list[i];
L->size--;
return temp;
}
//14、从线性表L中删除表尾元素并返回它,若删除失败则停止程序运行
ElemType DeleteLastList(struct List *L)
{
if (L->size == 0)
{
printf("线性表为空,不能删除!\n");
exit(1);
}
L->size--;
return L->list[L->size];
}
//15、从线性表L中删除第pos个元素并返回它,若删除失败则停止程序运行
ElemType DeletePosList(struct List *L, int pos)
{
ElemType temp;
int i;
if (pos < 1 || pos > L->size)
{
printf("pos值越界,不能删除!\n");
exit(1);
}
temp = L->list[pos - 1];
for (i = pos; i < L->size; i++)
L->list[i - 1] = L->list[i];
L->size--;
return temp;
}
//16、从线性表L中删除值为x的第一个元素,若删除成功返回1否则返回0
int DeleteValueList(struct List *L, ElemType x)
{
int i, j;
for (i = 0; i < L->size; i++)
if (L->list[i] == x)
break; //此时的i即是要删除的位置,
if (i == L->size)//若找不到,上面的i自增后为size
return 0;
for (j = i + 1; j < L->size; j++)
L->list[j - 1] = L->list[j];
L->size--;
return 1;
}
//主函数
void main()
{
int a[10] = {2,4,6,8,10,12,14,16,18,20};
int i;
struct List L;
InitList(&L, 5); //初始化分配线性表空间为5
for (i = 0; i < 10; i++)
InsertLastList(&L, a[i]); //将数组中的元素依次插入线性表(空间不够,扩大2倍)
InsertPosList(&L, 11, 48); //在第11位插入48(空间不够,再次扩大2倍)
InsertPosList(&L, 1, 64); //在第1位插入64
printf("%d\n", GetElem(&L, 4)); //输出第4个元素
TraverseList(&L); //遍历输出所有元素
printf("%d\n", FindList(&L, 10));//查找输出数值为10的元素位置(下标)
UpdatePosList(&L, 3, 20); //把第三个元素修改为20
DeleteFirstList(&L); //删除表头元素
DeleteFirstList(&L);
DeleteLastList(&L); //删除表尾元素
DeleteLastList(&L);
DeletePosList(&L, 5); //删除第5个元素
DeletePosList(&L, 7); //删除第7个元素
printf("%d\n", SizeList(&L)); //输出线性表长度
printf("%d\n", EmptyList(&L)); //判断线性表是否为空
TraverseList(&L); //遍历输出所有元素
ClearList(&L); //清空线性表,释放空间
}
运行结果:
3、单链表
在每个结点中除包含有数值域外,只设置一个指针域,用以指向其后继结点,这样构成的链接表被称为线性单向链接表,简称单向链表或单链表。
单链表存储结构示意图:
下面是通过一个实例实现单链表的操作,实例中包含16种操作实现,在其他程序中可直接套用:
//线性表在单链表上的操作实现
#include<stdio.h>
#include<stdlib.h>
#define NN 12
#define MM 20
typedef int ElemType; //定义元素类型
struct sNode //定义单链表结点类型
{
ElemType data;
struct sNode* next;
};
//1、初始化线性表,即置单链表的表头指针为空
void InitList(struct sNode** HL)
{
*HL = NULL;
}
//2、清除线性表L的所有元素,即释放单链表的所有结点,使之成为空表
void ClearList(struct sNode** HL)
{
struct sNode *cp, *np; //定义两个相邻结点的指针
cp = *HL; //头指针赋给cp
while (cp != NULL)
{
np = cp->next; //用np保存下一个结点的指针
free(cp); //释放cp指向的结点
cp = np; //使下一个结点成为当前结点
}
*HL = NULL; //置单链表的表头指针为空
}
//3、返回单链表的长度
int SizeList(struct sNode* HL)
{
int i = 0;
while (HL != NULL)
{
i++;
HL = HL->next;
}
return i;
}
//4、检查单链表是否为空,若为空则返回1否则返回0
int EmptyList(struct sNode* HL)
{
if (HL == NULL)
return 1;
else
return 0;
}
//5、返回单链表中第pos个结点的元素,若pos超出范围,则停止程序运行
ElemType GetElem(struct sNode* HL, int pos)
{
int i = 0;
if (pos < 1)
{
printf("pos值非法,退出运行!\n");
exit(1);
}
while (HL != NULL)
{
i++;
if (i == pos)
break;
HL = HL->next;
}
if (HL != NULL)
return HL->data;
else
{
printf("pos值非法,退出运行!\n");
exit(1);
}
}
//6、遍历一个单链表
void TraverseList(struct sNode* HL)
{
while (HL != NULL)
{
printf("%d,", HL->data);
HL = HL->next;
}
printf("\n");
}
//7、从单链表中查找具有给定值x的第一个元素的,成功返回结点data域的存储地址,否则返回NULL
ElemType* FindList(struct sNode* HL, ElemType x)
{
while (HL != NULL)
{
if (HL->data == x)
return &HL->data;
else
HL = HL->next;
}
return NULL;
}
//8、修改单链表中第pos个结点的值为x,成功返回1失败返回0
int UpdatePosList(struct sNode* HL, int pos, ElemType x)
{
int i = 0;
struct sNode* p = HL;
while (p != NULL)
{
i++;
if (pos == i)
break;
else
p = p->next;
}
if (pos == i)
{
p->data = x;
return 1;
}
else
return 0;
}
//9、向单链表表头插入一个元素
void InsertFirstList(struct sNode** HL, ElemType x)
{
struct sNode *newp;
newp = malloc(sizeof(struct sNode));
if (newp == NULL)
{
printf("内存动态空间用完,退出运行!\n");
exit(1);
}
newp->data = x;
newp->next = *HL;
*HL = newp; //把新节点作为新的表头结点
}
//10、向单链表的末尾添加一个元素
void InsertLastList(struct sNode** HL, ElemType x)
{
struct sNode *newp;
newp = malloc(sizeof(struct sNode));
if (newp == NULL)
{
printf("动态内存空间用完,退出运行!\n");
exit(1);
}
newp->data = x;
newp->next = NULL;
if (*HL == NULL) //若原表为空
*HL = newp;
else
{
struct sNode* p = *HL;
while (p->next != NULL)
p = p->next;
p->next = newp;
}
}
//11、向单链表中第pos个结点位置插入元素x,成功返回1失败返回0
int InsertPosList(struct sNode** HL, int pos, ElemType x)
{
int i = 0;
struct sNode *newp;
struct sNode *cp = *HL, *ap = NULL;
if (pos <= 0)
{
printf("pos值不正确,返回0表示插入失败!\n");
return 0;
}
while (cp != NULL)
{
i++;
if (pos == i)
break;
else
{
ap = cp;
cp = cp->next;
}
}
newp = malloc(sizeof(struct sNode));
if (newp == NULL)
{
printf("内存动态空间用完,无法插入!\n");
return 0;
}
newp->data = x;
if (ap == NULL) //插入表头的情况
{
newp->next = cp;
*HL = newp;
}
else //插入到ap与cp之间的情况
{
newp->next = cp;
ap->next = newp;
}
return 1;
}
//12、向有序单链表中插入元素x,使得插入之后仍然有序
void InsertOrderList(struct sNode** HL, ElemType x)
{
struct sNode* cp = *HL, *ap = NULL;
struct sNode *newp;
newp = malloc(sizeof(struct sNode));
if (newp == NULL)
{
printf("内存动态空间用完,退出运行!\n");
exit(1);
}
newp->data = x;
if (cp == NULL || x < cp->data) //把新结点插入到表头
{
newp->next = cp;
*HL = newp;
return;
}
while (cp != NULL)
{
if (x < cp->data)
break;
else
{
ap = cp;
cp = cp->next;
}
}
newp->next = cp; //把x结点插入到ap与cp之间
ap->next = newp;
}
//13、从单链表中删除头结点,并返回结点值,失败则停止程序运行
ElemType DeleteFirstList(struct sNode** HL)
{
ElemType temp;
struct sNode* p = *HL; //暂存表头结点指针
if (*HL == NULL)
{
printf("单链表为空,无表头删除,退出运行!\n");
exit(1);
}
*HL = (*HL)->next;
temp = p->data; //暂存表头元素
free(p);
return temp;
}
//14、从单链表中删除尾结点并返回它的值,失败则停止程序运行
ElemType DeleteLastList(struct sNode** HL)
{
ElemType temp;
struct sNode* cp = *HL;
struct sNode* ap = NULL;
if (cp == NULL)
{
printf("单链表为空,无表尾删除,退出运行!\n");
exit(1);
}
while (cp->next != NULL)
{
ap = cp;
cp = cp->next;
}
if (ap == NULL) //若单链表只有一个结点
*HL = (*HL)->next;
else
ap->next = NULL;
temp = cp->data;
free(cp);
return temp; //把新节点作为新的表头结点
}
//15、从单链表删除第pos个结点并返回它的值,失败则停止程序运行
ElemType DeletePosList(struct sNode** HL, int pos)
{
int i = 0;
ElemType temp;
struct sNode* cp = *HL;
struct sNode* ap = NULL;
if (cp == NULL || pos <= 0)
{
printf("单链表为空或pos值不正确,退出运行!\n");
exit(1);
}
while (cp != NULL)
{
i++;
if (i == pos)
break;
ap = cp;
cp = cp->next;
}
if (cp == NULL) //单链表中没有第pos个结点
{
printf("pos值不正确,退出运行!\n");
exit(1);
}
if (pos == 1) //删除表头结点
*HL = (*HL)->next; //或:*HL = cp->next;
else
ap->next = cp->next;
temp = cp->data;
free(cp);
return temp;
}
//16、从单链表中删除值为x的第一个结点,成功返回1失败返回0
int DeleteValueList(struct sNode** HL, ElemType x)
{
struct sNode* cp = *HL;
struct sNode* ap = NULL;
while (cp != NULL)
{
if (cp->data == x)
break;
ap = cp;
cp = cp->next;
}
if (cp == NULL) //单链表中不存在值为x的结点
return 0;
if (ap == NULL) //在表头位置
*HL = (*HL)->next; //或:*HL = cp->next;
else
ap->next = cp->next;
free(cp);
return 1;
}
//主函数
void main()
{
int a[NN];
int i;
struct sNode *p, *h, *s;
InitList(&p); //初始化,置单链表表头为空
for (i = 0; i < NN; i++) //产生12个随机数
a[i] = rand() % MM;
printf("随机数序列:");
for (i = 0; i < NN; i++)
printf("%d,",a[i]);
printf("\n");
printf("随机数逆序:");
for (i = 0; i < NN; i++)
InsertFirstList(&p, a[i]); //将a[i]倒序插入单链表
TraverseList(p); //遍历
printf("单链表长度:%d\n", SizeList(p)); //输出单链表长度
for (h = p; h != NULL; h = h->next) //每个元素循环与后面的元素进行比较,删除重复数
while (DeleteValueList(&(h->next), h->data));
printf("去除重复数:");
TraverseList(p); //遍历
printf("单链表长度:%d\n", SizeList(p)); //输出单链表长度
h = NULL;
for (s = p; s != NULL; s = s->next)//将上面的单链表的值依次插入新的一个空单链表,每步插入后都有序
InsertOrderList(&h, s->data);
printf("有序表序列:");
TraverseList(h); //遍历
ClearList(&p); //清除单链表
}
运行结果:
4、双链表
在每个结点中除包含有数值域外,设置有两个指针域,分别用以指向其前驱结点和后继结点,这样构成的链接表被称为线性双向链接表,简称双向链接表或双链表。
双链表存储结构示意图:
双链表的结点类型:
struct dNode
{
ElemType data;
struct dNode* left; //左指针域
struct dNode* right; // 右指针域
};
双链表的插入:若在双链表中p结点之后插入一个q结点
q->right = p->right;
if (p->right) //p结点有后继结点
p->right->left = q;
q->left = p;
p->right = q;
双链表的删除:若要删除双链表中p指针所指向的结点,并假设p结点前后都存在结点
p->left->right = p->right;
p->right->left = p->left;
5、循环链表
在单链表中,若让表尾结点的指针域指向表头结点,在双链表中,若让表尾结点的右指针域指向表头结点,而让表头结点的左指针域指向表尾结点,就构成了循环链表。
6.代表头附加结点的线性链表
在线性表的链接存储中,为了方便在表头插入和删除结点,使得与在其他地方所做的操作相同,需要在表头结点的前面增加一个结点,把它称为表头附加结点或头结点。