浅谈带头结点的单链表
1、头结点:在栈区开辟,指针域指向第一个首元结点,数据域不存储数据,可以存储当前结点的个数;
2、普通结点:无论是头结点还是普通结点都是一个结构体类型,由指针域和数据域组成;
指针域指向下一个结点,存储下一个结点的地址;
数据域可以设置成联合体类型,成员由数据元素和结点个数组成,之所以将数据域设置成联合体类型,是因为考虑到头结点数据域存储结点个数,普通结点数据域存储结点个数。
3、带头结点的单链表的基本操作
#define TRUE 1;
#define FALSE 0;
typedef int ElemType;
typedef union data//数据域:联合体类型,成员:数据元素,结点个数;
{
ElemType val;
int num;
}Data;
typedef struct Node//结点
{
Data data;//数据域:头结点存储当前结点的个数,头结点在栈区开辟,普通结点存储数据元素
struct Node * next;//指针域:指向下一个结点,存储下一个结点的地址
}LNode, * LinkList;
static LinkList ApplyNode(ElemType val, LinkList next)//申请一个新结点;
{
LinkList new_node = (LinkList)malloc(sizeof(LNode));
assert(new_node != NULL);
if (new_node == NULL)
{
return NULL;
}
new_node->data.val = val;
new_node->next = next;
return new_node;
}
static LinkList Find_Pos_P(LinkList head,int pos)//找pos的前一个结点
{
LinkList p = head;
while(pos > 0)
{
p = p->next;
pos--;
}
return p;
}
int Init_HeadLinklist(LinkList head)//初始化, next,data
{
if (head == NULL)
{
exit(0);
}
head->next = NULL;
head->data.num = 0;
return TRUE;
}
int Insert_HeadLinkList_Pos(LinkList head,ElemType val,int pos)//按位置插入 o(n)
{
if (head == NULL)
{
exit(0);
}
if (pos<0||pos>head->data.num)
{
return FALSE;
}
//找pos之前的结点
/*LinkList p = head;
while(pos > 0)
{
p = p->next;
pos--;
}*///写一个函数来代替找pos之前的结点
LinkList p = Find_Pos_P(head,pos);
LinkList new_node = ApplyNode(val,p->next);
p->next = new_node;
head->data.num++;
return TRUE;
}
int Delete_HeadLinkList_Pos(LinkList head,int pos)//按位置删除 o(n)
{
assert(head != NULL);
if (head == NULL)
{
return FALSE;
}
if (pos<0||pos>=head->data.num)
{
return FALSE;
}
LinkList p = Find_Pos_P(head,pos);
LinkList q = p->next;
p->next = q->next;
free(q);
q = NULL;
head->data.num--;
return TRUE;
}
LinkList FindNode_Pos(LinkList head,int pos)//按位置查找 o(n)
{
assert(head != NULL);
if (head == NULL)
{
exit(0);
}
if (pos<0||pos>=head->data.num)
{
return NULL;
}
LinkList p = Find_Pos_P(head,pos);
return p->next;
}
int Clear_HeadLinkList(LinkList head)//清空
{
assert(head != NULL);
if (head == NULL)
{
exit(0);
}
while(head->data.num > 0)
{
if (!Delete_HeadLinkList_Head(head))
{
return FALSE;
}
}
return TRUE;
}
int Destroy_HeadLinkList(LinkList head)//销毁
{
return Clear_HeadLinkList(head);
}
4、带头结点的单链表的经典面试题
只讲思路和方法,代码自己实现
第一题:找倒数第k个结点(不知道结点的个数)
1.1、知道结点个数:假设已知结点个数为n,倒数第k个结点,就是正数的第n-k个结点;
1.2、不知道结点个数:设置两个结点指针p和q,当q走到结尾的时候,p刚好走到倒数第k个结点处;
p即为所求的结点,p和q相差的结点数就为k;
注意:k要判断合法性;
第二题:在时间复杂度为O(1)的情况下删除结点p(p不是尾结点)
2.1、删除结点p要找p的前一个结点,如果按照这种方法,时间复杂度为o(n),不符合要求;
2.2、删除一个结点其实是删除这个结点的数据,那么可以找结点p的直接后继q,将p的直接后继q中的数据复制给p
那么将q->next赋给p->next,在将q给删了,从而间接删除了p;
这就是为什么结点p不能是尾结点,因为尾结点的直接后继为NULL;
第三题:单链表的原地逆置
3.1、设置3个指针变量求解,s=NULL,p=head->next,q=p->next;
3.2、只设置两个指针变量,p=head->next,q=p->next,head->next=NULL,单链表的原地逆置就相当于头插;
第四题:判断两个单链表是否相交,找到相交的一个结点
解:y型结构:两个单链表相交;
设置两个指针变量p和q(p和q分别代表两条单链表);
p和q同时遍历一遍的长度之差就是他们相交之前的长度之差。
因此可以让长度较长的单链表先走一个差值,然后在同时遍历,p和q相等的地方就是相交点。
第五题:判断单链表是否有环,找到入环的第一个结点
解:第一步判断单链表是否有环:需要设置快慢指针,慢指针p一次走一个(p=p->next)
快指针q一次走两个(q=q->next->next),当p和q相等时说明单链表有环。
第二步找到入环的第一个结点:
x为从头到入环的第一个结点的路径,y为入环结点到pq相等处的路径,z为pq相等处向后走到入环结点的路径;
p的路径:x+y; q的路径:x+(y+z)n+y;
找关系:2(x+y)=x+(y+z)n+y->x+y=(y+z)n->x=(y+z)(n-1)循环路径+z->x=z;
即p从头开始走,q从pq相等处开始走,pq相等处即为入环结点。(注意此时的p和q都是一次走一个);