一、顺序表
顺序表定义 :顺序表是在计算机内存中以数组的形式保存的线性表,线性表的顺序存储是指用一组地址连续的存储单元依次存储线性表中的各个元素、使得线性表中在逻辑结构上相邻的数据元素存储在相邻的物理存储单元中,即通过数据元素物理存储的相邻关系来反映数据元素之间逻辑上的相邻关系,采用顺序存储结构的线性表通常称为顺序表。顺序表是将表中的结点依次存放在计算机内存中一组地址连续的存储单元中。
顺序表可以分为静态顺序表和动态顺序表,静态较为简单,本文提供全部动态顺序表基本操作的代码。
顺序表的基本操作:
1、顺序表的构建 (采用结构体构建顺序表)代码如下:
#define MAXSIZE 100
typedef int Datatype;
typedef struct SequenceTable //静态顺序表
{
Datatype _data[MAXSIZE];
Datatype _size;
}Seq;
typedef struct SeqList //动态顺序表
{
Datatype *_data;
Datatype _size;
size_t _capacity;
}SeqList;
2、顺序表的初始化
void SeqListInit(SeqList *list)
{
assert(list);
list->_capacity = 0;
list->_size = 0;
list->_data = NULL;
}
3、顺序表的增,删,查,改,打印,求元素个数,销毁顺序表
void PrintSeqList(SeqList *list) //打印顺序表
{
int i = 0;
assert(list);
for(i=0; i<list->_size; i++)
printf("%d ",list->_data[i]);
printf("\n");
}
void PushSeqlist(SeqList* list,Datatype pos,Datatype x) //插入元素
{
int i = list->_size;
assert(list);
if (pos>list->_size)
return;
CheckSeqCapacity(list);
while (i != pos)
{
list->_data[i] = list->_data[i-1];
i--;
}
list->_data[i] = x;
list->_size++;
}
void CheckSeqCapacity(SeqList *list) //判断顺序表是否需要扩容
{
assert(list);
if (list->_capacity == list->_size)
{
Datatype *tmp = (Datatype*)realloc(list->_data,(list->_capacity * 2+3)*sizeof(Datatype));
assert(tmp);
list->_data = tmp;
list->_capacity = list->_capacity*2+3;
}
}
void DeleteSeqlist(SeqList *list,Datatype x) //删除元素
{
int pos = SearchSeqlist(list,x);
assert(list);
if (pos>=0)
{
int i = pos;
while (i < list->_size-1)
{
list->_data[i] = list->_data[i+1];
i++;
}
list->_size--;
}
}
size_t SeqlistSize(SeqList *list)//求顺序表元素个数
{
assert(list);
return list->_size;
}
Datatype SearchSeqlist(SeqList *list,Datatype x) //在顺序表中查找元素
{
int i = 0;
assert(list);
for(i=0; i<list->_size; i++)
{
if (list->_data[i] == x)
return i;
}
return -1;
}
void ChangeSeqlist(SeqList *list,Datatype x,Datatype dst) //修改元素
{
int pos = SearchSeqlist(list,x);
assert(list);
if (pos>=0)
list->_data[pos] = dst;
}
void ClearSeqlist(SeqList *list) //清空顺序表(顺序表的内存实在堆上用realloc开辟的,必须手动释放,防止内存泄漏)
{
assert(list);
list->_size = 0;
list->_capacity = 0;
free(list->_data);
list->_data = NULL;
}
顺序表的基本操作较为简单,来说说顺序表的优缺点:
原理:顺序表存储是将数据元素放到一块连续的内存存储空间,存取效率高,速度快。但是不可以动态增加长度
优点:存取速度高效,通过下标来直接存储
缺点:1.插入和删除比较慢,2.不可以增长长度
比如:插入或者删除一个元素时,整个表需要遍历移动元素来重新排一次顺序
链表:(单链表,双链表(无头单双链表,带头节点单双链表,循环单双链表))
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。
链表的基本操作:(本博文写出了单链表的基本操作)
链表的结构的构建
typedef int Datatype;
typedef struct LinkList //单链表结构
{
Datatype _data;//数据域
struct LinkList *_next; //指针域
}LinkList;
typedef struct doublelist //双链表
{
Datatype _data;
struct doublelist *_prev; //前趋
struct doublelist *_next; //后继
}doublelist;
节点的创建
LinkList* BuyListNode(Datatype x) //创建节点
{
LinkList *newNode = (LinkList *)malloc(sizeof(LinkList));
assert(newNode);
newNode->_next = NULL;
newNode->_data = x;
return newNode;
}
单链表的增,删,查,改,打印,排序,逆置,销毁。
void PrintLinkList(LinkList *phead) //链表打印
{
assert(phead);
while (phead)
{
printf("%d ",phead->_data);
phead = phead->_next;
}
printf("\n");
}
void LinkListInsert(LinkList **pphead,LinkList *pos,Datatype x) //链表的插入(包括头插,随机插入)
{
LinkList *newNode = BuyListNode(x);
assert(pphead);
if (*pphead == NULL)
{
newNode->_next = *pphead;
*pphead = newNode;
}
else
{
pos->_data ^= newNode->_data;
newNode->_data ^= pos->_data;
pos->_data ^= newNode->_data;
newNode->_next = pos->_next;
pos->_next = newNode;
}
}
void LinkListDelete(LinkList **pphead,Datatype x)//链表元素的删除
{
LinkList *tmp =LinkListSearch(*pphead,x);
LinkList *phead = *pphead;
assert(pphead);
if (tmp == *pphead) //头删
*pphead = tmp->_next;
else if (tmp)
{
if (tmp->_next) //中间删除
{
LinkList *cur = tmp->_next;
tmp->_data = cur->_data;
tmp->_next = cur->_next;
free(cur);
cur = NULL;
}
else //尾删
{
LinkList * cur = NULL;
while (phead->_next)
{
cur = phead;
phead = phead->_next;
}
cur->_next = NULL;
free(tmp);
tmp = NULL;
}
}
}
LinkList *LinkListSearch(LinkList *phead,Datatype x) //链表元素查找
{
assert(phead);
while (phead)
{
if (phead->_data == x)
{
return phead;
}
phead = phead->_next;
}
return NULL;
}
void LinkListChange(LinkList *phead,Datatype x,Datatype y) //链表元素修改
{
LinkList *tmp = LinkListSearch(phead,x);
assert(phead);
if (tmp)
tmp->_data = y;
}
void DestoryLinkList(LinkList *list) //链表的销毁(节点是一个一个开辟的,必须单个节点释放内存)
{
LinkList *tmp = list;
while (list)
{
list = tmp;
tmp = tmp->_next;
free(list);
list = NULL;
}
}
void LinkListSort(LinkList *phead)//链表排序(选择法排序)
{
int len = 0;
int i = 0;
LinkList *tmp = phead,*flag = NULL;
assert(phead);
while(tmp) //元素个数
{
len++;
tmp = tmp->_next;
}
while(--len)
{
int num = 0;
LinkList *cur = NULL;
tmp = phead;
flag = tmp;
for (i = 0; i<len; i++)
{
cur = tmp->_next;
if (flag->_data > cur->_data)
flag = cur; //记录最大数
tmp = tmp->_next;
}
num = flag->_data; //将最大数放在最后
flag->_data = tmp->_data;
tmp->_data = num;
}
}
LinkList *LinkListReverse(LinkList *pphead) //单链表的逆置
{
assert(pphead);
if (pphead->_next) //有1个以上节点
{
LinkList *cur = pphead;
LinkList *next = cur->_next;
cur->_next = NULL;
while (next) //逆置过程
{
LinkList *tmp = next->_next;
next->_next = cur;
cur = next;
next = tmp;
}
return cur; //返回新的头节点
}
else
return pphead;
}
链表的优缺点:
原理:链表存储是在程序运行过程中动态的分配空间,只要存储器还有空间,就不会发生存储溢出问题
优点:插入和删除速度快,保留原有的物理顺序,比如:插入或者删除一个元素时,只需要改变指针指向即可
缺点:查找速度慢,因为查找时,需要循环链表访问
从它们的存储优缺点来看,各自有各自的使用场景,比如:频繁的查找却很少的插入和删除操作可以用顺序表存储,如果频繁的插入和删除操作很少的查询就可以使用链表存储
链表相关面试题:
链表的从尾到头打印
void PrintLinkListrR(LinkList *phead) //递归打印
{
if (phead == NULL)
return;
PrintLinkListrR(phead->_next);
printf("%d ",phead->_data);
}
void PrintLinkListTail(LinkList *phead) //利用尾指针前移依次打印
{
LinkList *tail = NULL;
LinkList *tmp = phead;
assert(phead);
while (tail != phead)
{
tmp = phead;
while (tmp->_next != tail)
tmp = tmp->_next;
printf("%d ",tmp->_data);
tail = tmp;
}
}
删除一个无头单链表的非尾节点(不能遍历链表)
void LinkListNoHead(LinkList *pos)
{
LinkList *tmp = pos->_next; //保存pos下一个位置
assert(pos);
pos->_data = tmp->_data;
pos->_next = tmp->_next;
free(tmp);
tmp = NULL;
}
在无头单链表的一个节点前插入一个节点(不能遍历链表)
void LinkListNoHeadInsert(LinkList *pos,Datatype x)
{
LinkList *newNode = BuyListNode(x);
assert(pos);
newNode->_next = pos->_next; //插入节点
pos->_next = newNode;
//交换数据
pos->_data ^= newNode->_data;
newNode->_data ^= pos->_data;
pos->_data ^= newNode->_data;
}
单链表实现约瑟夫环(JosephCircle)
void LinkListJosephCircle(LinkList *phead,Datatype x)
{
LinkList *tail = phead;
int count = 0;
assert(phead);
while (tail->_next)
tail = tail->_next;
tail->_next = phead; //构成环
while (phead != phead->_next)
{
if (count == (x-1)) //到了指定元素就删掉该元素
{
LinkList *tmp = phead->_next;
phead->_next = tmp->_next;
count = 0;
free(tmp);
tmp = NULL;
}
phead = phead->_next;
count++;
}
printf("%d \n",phead->_data); //最后剩下的一个元素
}
单链表排序(冒泡排序)
void Swap(int* p1,int* p2)
{
int tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
void LinkListBubbleSort(LinkList *phead)
{
int flag = 1;
LinkList *tail =NULL;
LinkList *tmp = phead;
assert(phead);
while (tmp->_next != tail) //冒泡总次数
{
LinkList *cur = phead;
while (cur->_next != tail) //每次冒泡多少个数
{
if (cur->_data > cur->_next->_data)
{
flag = 0;
Swap(&cur->_data,&cur->_next->_data);
}
cur = cur->_next;
}
tail = cur; //该指针前移
if (flag) //优化冒泡次数
return;
}
}
合并两个有序链表,合并后依然有序
LinkList *CombineDoublelinklist(LinkList *list1,LinkList *list2)
{
LinkList *list = NULL;
LinkList *tail = NULL;
assert(list1 && list2);
if(list1->_data < list2->_data) //找出小的一个做表头
{
list = list1;
list1 = list1->_next;
}
else
{
list = list2;
list2 = list2->_next;
}
tail = list;
while (list1->_next && list2->_next)
{
if(list1->_data < list2->_data) //把小的一个链在tail后面
{
tail->_next = list1;
list1 = list1->_next;
}
else
{
tail->_next = list2;
list2 = list2->_next;
}
tail = tail->_next;
}
if (list1->_next) //把不为空的链表链接起来
tail->_next = list1;
else
tail->_next = list2;
return list;
}
查找单链表的中间节点,要求只能遍历一次链表
LinkList *SearchMidNode(LinkList *phead)
{
LinkList *fast = phead;
LinkList *slow = phead;
assert(phead);
while (fast && fast->_next ) //快指针的速度是慢指针的二倍。
{
fast = fast->_next->_next;
slow = slow->_next;
}
return slow;
}
查找单链表的倒数第k个节点,要求只能遍历一次链表
LinkList *SearchKNode(LinkList *phead,Datatype k)
{
LinkList *fast = phead;
LinkList *slow = phead;
assert(phead);
while (k--) //先走k步
fast = fast->_next;
while (fast) //在同时走
{
fast = fast->_next;
slow = slow->_next;
}
return slow;
}
删除链表的倒数第K个结点
void DeleteKNode(LinkList *phead,Datatype k)
{
LinkList *fast = phead;
LinkList *slow = phead;
LinkList *tmp = slow;
assert(phead);
while (k--) //先走k步
fast = fast->_next;
while (fast) //在同时走
{
tmp = slow;
fast = fast->_next;
slow = slow->_next;
}
tmp->_next = slow->_next;
free(slow);
slow = NULL;
}
判断单链表是否带环?若带环,求环的长度?求环的入口点?
void CreateCircle(LinkList *list,LinkList *entry) //创建一个环用于测试
{
assert(list);
while (list->_next)
{
list = list->_next;
}
list->_next = entry; //构成环
}
LinkList *CheckIsCircle(LinkList *phead) //带环返回环中相遇点,防止返回null
{
LinkList *fast = phead;
LinkList *slow = phead;
assert(phead);
do
{
fast = fast->_next->_next;
slow = slow->_next;
}while(fast != slow && fast);
if (fast)
return slow;
return NULL;
}
int CircleSize(LinkList *phead,LinkList *meet)
{
int size = 0;
LinkList *tmp = meet;
assert(phead);
do
{
tmp = tmp->_next;
size++;
}while (meet != tmp);
return size;
}
LinkList *CircleEntry(LinkList *phead,LinkList* meet)
{
LinkList *start = phead;
assert(phead);
while (start != meet)
{
start = start->_next;
meet = meet->_next;
}
return meet;
}
判断两个链表是否相交,若相交,求交点。(假设链表不带环)
void CreateTogetherNode(LinkList *list1,LinkList *list2,LinkList *node1,LinkList *node2) //创造相交链表用于测试
{
assert(list1 && list2);
node2->_next = node1;
}
static LinkList *LinkListtTogetherNode(LinkList *list1,LinkList *list2) //求交点地址
{
int m1 = 0;
int m2 = 0;
LinkList *tmp1 = list1;
LinkList *tmp2 = list2;
while (tmp1 || tmp2) //求出两个链表总长度
{
if (tmp1)
{
m1++;
tmp1 = tmp1->_next;
}
if (list2)
{
m2++;
tmp2 = tmp2->_next;
}
}
tmp1 = list1;
tmp2 = list2;
if (m1>m2) //长的先走
{
int len = m1 - m2;
while (len--)
tmp1 = tmp1->_next;
}
else
{
int len = m2 - m1;
while (len--)
tmp2 = tmp2->_next;
}
while (tmp1 != tmp2) //一样长了同时走,走道交点处停下。
{
tmp1 = tmp1->_next;
tmp2 = tmp2->_next;
}
return tmp1; //返回交点地址
}
LinkList *LinkListSameNode(LinkList*list1,LinkList *list2) //判断是否相交,相交返回交点,否则返回空
{
LinkList *m1 = list1;
LinkList *m2 = list2;
assert(m1 && list2);
while (m1->_next)
m1 = m1->_next;
while (m2->_next)
m2 = m2->_next;
if(m1 == m2) //相交
{
return LinkListtTogetherNode(list1,list2);
}
return NULL;
}
/判断两个链表是否相交,若相交,求交点。(假设链表可能带环)【升级版】
分情况(1、不相交 2、相交不带环 3、相交带环(a、环内相交 b、环外相交) )
static LinkList *LinkListCircleNode(LinkList *list1,LinkList *meet1,LinkList *list2,LinkList *meet2) //求带环相交的交点
{
LinkList *m1 = CircleEntry(list1,meet1); //求得入口地址
LinkList *m2 = CircleEntry(list2,meet2);
assert(list1 && list2 && meet1 && meet2);
if (m1 != m2)//环内相交
return m1;
else //环外相交
{
m1 = NULL;
m2 = NULL;
return LinkListtTogetherNode(list1,list2);
}
}
LinkList *LinkListSameNodeCircle(LinkList *list1,LinkList *list2)
{
LinkList *node = NULL;
LinkList *meet1 = CheckIsCircle(list1);
LinkList *meet2 = CheckIsCircle(list2);
assert(list1 && list2);
if (meet1 && meet2) //都带环
{
LinkList *tmp1 = meet1->_next;
LinkList *tmp2 = meet2;
while (tmp1 != meet1)
{
if (tmp1 == tmp2) //带环相交
{
node = LinkListCircleNode(list1,meet1,meet2,list2);
return node;
}
tmp1 = tmp1->_next;
}
return NULL; //带环不相交
}
else if (meet1 == NULL && meet2 == NULL) //两链表都不带环
{
node = LinkListSameNode(list1,list2);
if(node) //不带环相交
return node;
return NULL; //不带环不相交
}
else //其他不相交的情况
return NULL;
}
求两个已排序单链表中相同的数据。
void TwoListSamedata(LinkList *list1,LinkList *list2)
{
assert(list1 && list2);
LinkListBubbleSort(list1); //排序两链表
LinkListBubbleSort(list2);
while (list1 && list2)
{
if (list1->_data < list2->_data)
list1 = list1->_next;
else if(list2->_data< list1->_data)
list2 = list2->_next;
else
{
printf("%d ",list2->_data); //相等就打印出来,当然这里也可以把相等的数据保存起来,单独打印
list1 = list1->_next;
list2 = list2->_next;
}
}
printf("\n");
}
总结:
顺序表存储位置是相邻连续的,可以随即访问的一种数据结构,一个顺序表在使
用前必须指定起长度,一旦分配内存,则在使用中不可以动态的更改。他的优点是访问数据是比较方便,可以随即的访问表中的任何一个数据,缺点是定义的长度不可更改造成存储空间的浪费。
链表是通过指针来描述元素关系的一种数据结构,他可以是物理地址不连续的物理空间。不能随即访问链表元素,必须从表头开始,一步一步搜索元素。它的优点是:对于数组,可以动态的改变数据的长度,分配物理空间。
建议:
在使用中如果一个数组在使用中,查询比较多,而插入,删除数据比较少,数组的长度不变时,选顺序表比较合理。如果插入,删除,长度不定的数组,可以选链表。