数据结构随笔——链表及双向链表,循环链表
一、单向链表
在链表中,我们为了表示前一个数据和后一个数据的逻辑对于关系,比如说两个数据,ai,ai+1。那么在ai中除了存储其本身的数据,还需要存储指示其后继信息,举例为:ai+1的地址。我们把存储数据元素信息的域称为数据域,把存储直接后继位置的域称为指针域。指针域中存储的信息称做指针或链。这两部分信息组成数据元素ai的存储映像,称为结点(Node)。
第一个节点的存储位置叫做头指针。最后一个节点的的指针指向NULL。
结点的存储结构:
struct node {
//data,这里用int类型代替
int data;
struct node* next;
};
node* listhead;//定义了头指针
链表的创建:
创建表的过程,这个操作是动态的,那么我们需要以此建立并插入。需要声明一指针,p,计数器变量。每创建一个结点,就把上一个结点的指针指向新创建的结点,然后在此节点输入数据,以此类推。个人倾向于创建时检查指针是非为空防止程序崩溃。
void creat(node* head,int n)
{
node* p;
node* r;
int i;
head=(struct node *)malloc(sizeof(node));
//让link指向新的空间,但头结点无数据!
r=head; //r为指向尾部的结点
listhead = head;
//listhead为头指针,head为当前头指针,listhead是全局变量
for (i = 0; i < n; i++)
{
if(p = (struct node*)malloc(sizeof(node)))
//检查是否创建成功,也可省略,不建议省略
{
p->data = rand() % 100;//随机插入一个数作为data
r->next = p;
r = p;
}
else
{
return;
}
}
if (r == NULL) //防止为空
return;
r->next = NULL;
}
单链表的插入
这步骤很简单,假设存储单元为e,只需创建一个结点,新节点的指针等于上一个结点的指针指向(就是新节点的下一个结点的位置),上一个结点的指针等于新结点的地址。

//插入,假设在位置i插入一个结点,数据为e
void insert( int i, int e)
{
int j = 1;
node* p;
p = listhead;
while (p && j < i)//寻找第i个结点
{
p = p->next;
j++;
}
if (!p || j>i)
return;
node* s;
s = (struct node*)malloc(sizeof(node)); //分配空间,创造一个结点s,则结点p在s的上一位
if (s == NULL)
return;
s->data = e;
s->next = p->next;
p->next = s;
return;
}
单链表的删除操作
遍历链表,找到符合条件的,若未找到,则说明不存在,找到则要删除的结点的指针,先赋值,q=p->next,p->next=q->next,意思为p的后继的后继结点改为p的后继结点。

void Delete (int i)//传入参数随条件可以改变
{
int j = 1;
node* p;
node* q;
p = listhead;
while (p && j < i)//寻找第i个结点,j==i时,p为第i-1个结点
{
p = p->next;
j++;
}
if (p->next == NULL||p==NULL)//该结点不存在
return;
q = p->next; //q为要删除的结点
cout << q->data << endl;
p->next = q->next;
free(q);
return;
}
链表的循环删除
这个很简单,找到头指针,定义一个p,q 。q=p->next,free(p),然后p,q,互换即可,注意最后的头指针一定要定义为NULL!
在程序运行期间,用动态内存分配函数来申请的内存都是从堆上分配的,动态内存的生存期自己来决定,使用非常灵活,但也易出现内存泄漏的问题,为了防止内存泄漏的发生,必须及时调用free()释放已不再使用的内存
void clearlist()
{
node* p;
node* q;
p = listhead;
while (p != NULL)
{
q = p->next;
free(p);
p = q;
}
listhead = NULL;
return;
}
二、循环链表
对于单链表,由于每个结点只存储了向后的指针,到了尾标志就停止了向后链的操作,这样,当中某一结点就无法找到它的前驱结点了,就像我们刚才说的,不能回到头部。
将单链表中终端结点的指针端由空指针改为指向头结点,就使整个单链表形成一个环,这种头尾相接的单链表称为单循环链表,简称循环链表。其从刚才的例子,可以总结出,循环链表解决了一一个很麻烦的问题。如何从当中一个结点出发,访问到链表的全部结点。

其实循环链表和单链表的主要差异就在于循环的判断条件上,原来是判断p->next是否为空,现在则是p -> next不等于头结点,则循环未结束。
但我们要是访问最后一个结点,仍然需要O(N)的复杂度,那么可以定义一个尾指针,rear,则rear->next,则为开始结点(具体看如何创建链表)。

三、双向链表
为了克服单向性这一缺点,我们的老科学家们,设计出了双向链表。双向链表(double linked list) 是在单链表的每个结点中,再设置一个指向其前驱结点的指针域。所以在双向链表中的结点都有两个指针域,一个指向直接后继,另一个指向直接前驱。
双向链表是单链表中扩展出来的结构,所以它的很多操作是和单链表相同的。比如求长度,遍历,但删除单个结点,插入之类的操作就需要麻烦一些了,顺序不能错。

插入

s->prior=p; //前继指针指向P
s->next=p->next; //先别改p,s的后继指向原来p的后继
p->next->prior=s;
//p此时未改,p的后继的前继等于ai+1的地址,那么改为s的地址
p->next=s;
删除

p->prior->next=p->next;
p->next->prior=p->prior;
浙公网安备 33010602011771号