1、单链表的定义:
typedef struct LNode{
ElemType data;
struct LNode *next;
}LNode,*LinkList;
LNode表示节点,LinkList指针可以理解为链表,data数据域,next指针域。
2、头插法:
LinkList headinsert(LinkList &L)
{
L=(LinkList)malloc(sizeof(LNode));
L->next=NULL;
int x;
LNode *s;
scanf("%d",&x);
while(x!=1)
{
s=(LNode*)malloc(sizeof(LNode));
s->data=x;
s->next=L->next;
L->next=s;
scanf("%d",&x);
}
return L;
}
头插法是链表逆置的关键方法,就是在起始端后的第一个位置不断插入新的节点,这里对应语句s->next=L->next;这是新点和后面的链接关系,还要注意头部的链接:L->next=s;
注意头插法的L->next=NULL的操作,不要忘了,在一些逆置问题中是需要拿下头结点的。
3、尾插法:
LinkList Tailinsert(LinkList &L)
{
LNode *r,*s;
r=L;
while(r->next!=NULL)
{
s=(LNode*)malloc(sizeof(LNode));
int a=0;
//加个循环终止条件,比如:a=999
cin>>a;
if(a==999) {r->next=NULL;return L;}
s->data=a;
r->next=s;
r=s;
}
return L;
}
尾插法是一种关键方法,它使得链表按照输入的节点的顺序得到定义,注意r是尾指针,最后一步尾指针的指针域为NULL。注意r=s的节点移动,不要忘了。
4、按序号查找
LNode *search(LinkList &L,int i)
{
if(i==0)
return L;
if(i<1)
return NULL;
LNode *s=L->next;
for(int j=1;j<i;j++)
s=s->next;
return s;
}
第一步合法性判断,如果第0号,返回一个头指针,如果小于1(等于0不包括)不合法,返回一个NULL。
第二步创个指针s,它即对应头指针指向头结点的指针域,即第一个元素的地址,然后不断查找就可以了。
5、按值查找结点
LNode *search2(LinkList &L,int i)
{
LNode *s=L->next;
while(1)
{
if(s->data==i) return s;
else s=s->next;
if(s==NULL) return s;
}
return s;
}
首先s指向第一个元素,然后找数值域就行了,走到尾部发现还是没有,就返回一个NULL。
6、插入结点(插入到第i个位置)
LinkList insert(LinkList L,int i,int x)
{
LNode *s=search(L,i-1);//按序查出对应结点的前驱结点地址
LNode *ss;
ss=(LNode*)malloc(sizeof(LNode));
ss->data=x;
ss->next=s->next;
s->next=ss;
return L;
}
无论插入还是删除,对于单链表而言,第一步永远是查出待插入位置的前驱结点的地址。
7、删除节点(删除第i个位置)
LinkList delete1(LinkList &L,int i)
{
LNode *s=search(L,i-1);
LNode *ss=s->next;
s->next=ss->next;
free(ss);
}
找到前驱结点后,令ss保存待删除节点的位置,如果直接用s->next->next,经过一系列操作后会丢失删除节点的位置,仅是建立前驱和后继结点的连接关系,所以ss=s->next是有必要的。
8、求表长
int length(LinkList &L)//求表长
{
LNode *s=L->next;
for(int i=1;;i++)
{
if(s==NULL) return i;
else s=s->next;
}
}
注意s=L->next对应i=1,表示从第一个元素开始,如果不是从第一个元素开始,应该写作s=L;i=0。
9、双链表的定义
typedef struct DLNode {
ElemType data;
struct DLNode *next,*prior;
}DLNode,*LinkList
就是多了一个指向前面的指针prior。
10、双链表的创建(尾插法)
DLinkList establish(DLinkList &L)//创建双链表
{
L=(DLinkList)malloc(sizeof(DNode));
DNode *s,*r;
r=L;
int a=0;
for(int i=0;;i++)
{
if(i==999) {r->next=NULL;break;}
else{
cin>>a;
s=(DNode *)malloc(sizeof(DNode));
s->data=a;
r->next=s;
s->prior=r;
r=s;
}
}
return L;
}
这里记得双向链接,写了r->next后不要忘了s->prior=r;
不要忘了移动:r=s;
11、双链表的插入(在结点p后插入结点)
DLinkList dinsert(DLinkList &L,DNode *p,int a)//在p后面插入节点
{
DNode *s;
s=(DNode *)malloc(sizeof(DNode));
s->data=a;
p->next->prior=s;
s->next=p->next;
s=p->next;
s->prior=p;
return L;
}
就是建立被插入结点和前驱和后继结点的双向链接关系,总共四个链接,对应四个语句,少一个不对。
12、双链表的删除(删除结点p的后继结点)
DLinkList Ddelete1(DLinkList &L,DNode *p)//删除节点p的后继节点
{
DNode *s=p->next;
p->next->next->prior=p;
p->next=p->next->next;
free(s);
return L;
}
和单链表的删除一致,需要现将后继元素的位置存储起来,避免操作丢失这个地址,然后建立两个链接关系,对应两个语句即可。
13、双链表的删除(删除节点p的前驱节点)
DLinkList Ddelete2(DLinkList &L,DNode *p)//删除节点p的前驱节点
{
DNode *s=p->prior;
p->prior=s->prior;
s->prior->next=p;
free(s);
return L;
}
这个同理,不做过多解释。
14、循环链表的建立
LinkList cycle(LinkList &L)//创建循环链表
{
LNode *s,*r;
r=L;
for(int i=0;;i++)
{
if(i==99999) {r->next=L;return L;}
else{
s=(LNode*)malloc(sizeof(LNode));
int a=0;
cin>>a;
s->data=a;
r->next=s;
r=s;
}
}
}
循环跳出条件满足后,令尾指针r的指针域指向头结点。
常规操作部分如上,下面来骚操作部分:
1、带头结点的单链表就地逆置,空间复杂度O(1)
LinkList reverse (LinkList &L)
{
LNode *s=L->next;
L->next=NULL;//头插法第一步,头指针指针域置NULL,即取下头结点
while(s!=NULL)
{
L->next=s->next;
L->next=s;
s=s->next;
}
return L;
}
其实就是头插法。
2、快慢指针找特定位置的元素
快慢指针被用来解决两种问题,第一种是为了减少求倒数第k个元素,为了减少时间复杂度,替代递归和栈的方法。第二种是找中间位置,即链表的中间元素。
第一个问题,找倒数第k个元素,如果找到,输出数据域,return1,找不到return 0;
int searchK (LinkList &L,int k)
{
LNode *quick=L->next;
LNode *slow=L->next;
int j=1;
while(quick!=NULL)
{
quick=quick->next;
if(j!=k) j++;
if(j==k)
slow=slow->next;
}
if(slow!=NULL&&j==k) {cout<<slow->data;return 1;}
return 0;
}
快指针走到正数第k个位置时慢指针开始走,快指针走到尽头的时候慢指针指向的刚好是倒数第k个位置。
注意条件的判断,如果j==k的话,j++其实也没必要做,因为j只有一个使命,就是提醒慢指针开始工作。
第二个问题,找到链表的中间元素
LNode *findmid(LinkList &L)
{
LNode *quick=L->next;
LNode *slow=L->next;
while(quick->next!=NULL&&slow!=NULL)
{
slow=slow->next;
quick=quick->next;
if(quick->next!=NULL) quick=quick->next;
}
return slow;
}
注意条件是quick的next不为空,slow不为空,不是slow的next不为空,因为慢指针是一步一步走的,尾端元素是最后一步操作,而对于两步两步走的快指针,第二步总是要判断一下结点是否在尾端,在尾端的话再走两步就越界了。循环终止条件满足后,slow指针对应的就是中间元素。