数据结构之链表

来源:互联网 发布:密码破解软件 编辑:程序博客网 时间:2024/06/05 11:53

一、为什么要引入链表?

1、数组的特点:

①数据类型一样的,为了解决数组的数据类型是一样的缺点,引入了结构体!

②分布的空间是连续的;

③数组的大小一但确定就很难改变,重新定义一个char a[20],然后将char a[10]里面的数据复制到a[20];(C++,Java里面是有可变数组的)

2、链表的概念:

可以这么说:链表一个可以变化的数组,可以由一个结点连接另一个结点,然后再由这个结点连接到另外的结点,形成一个锁链,锁链连接的是一个一个结点,也就是我们保存

的数据,锁链就是地址,第一个结点里面保存第二个结点的地址,第二个结点保存第三个结点的地址,以此类推……

3、结点的构建:结构体来作为一个结点:

关键的一步:构造一个结构体类型:

struct node
{
int data;         //保存的本身的数据;
struct node *pNext;//结构体类型的指针,保存下一个结构体的地址;    指针域;

};

现在没有给它分配内存空间,因为只是定义了一个结构体的类型,并没有定义具体的结构体类型的变量;

struct node  = int; //地位是等价的


struct node Node1; //定义一个结构体类型的变量;
struct node Node2; //定义一个结构体类型的变量;
struct node Node3; //定义一个结构体类型的变量;

因为可以通过Node1找到Node2,由Node2找到Node3,最关键的一步找到Node1,引入了“头指针”概念!

头指针就是第一个结点的位置,或者地址!

只有创建若干个结点之后,才能够把这些结点连接在一起----》创建结点;

创建一个结点:

void create_node(int data)

{

1、分配空间:因为链表要随时能够添加和删除,采用的是“堆空间”;

2、检查堆空间是否分配成功;

3、清空申请到的堆空间;

4、填充堆空间;给data赋值!

5、让pNext执行NULL;
}


4、具体的操作(带头节点):

①插入:

尾部插入:

1、首先找到链表的最后一个结点,也就是尾节点;

2、将原来的尾节点指向新结点;

3、将新结点的pNext指向NULL;

void insert_tail(struct node *pHeader,struct node *new)
{
struct node *p = pHeader;          //定义一个结构体类型的执行指向链表的头指针;

//1、首先找到链表的最后一个结点,也就是尾节点;

while( p->pNext != NULL)         //保证p不是尾节点
{
p = p->pNext; //往后移动一个结点;
}

//2、将原来的尾节点指向新结点;

p->pNext = new;

//3、将新结点的pNext指向NULL;

new->pNext = NULL;
}



头插:从结点的前面插入

1、将新结点的pNext指向原来的第一个有效结点;

2、将头结点的pNext指向待插入的新结点;

void insert_head(struct node *pHeader,struct node *new)
{
//1、将新结点的pNext指向原来的第一个有效结点;

new->pNext = pHeader->pNext;

//2、将头结点的pNext指向待插入的新结点;

pHeader->pNext = new;

}
注意点:上面的两步操作的顺序不能颠倒!

②遍历:

大家牢记一点,链表是用来存储数据的。既然是存储数据,有存肯定有取。

遍历的要求:不能重复、不能遗漏、尽可能追求效率的最大化。

伪代码:

1、找到链表的第一个有效结点,取出数据!查看他的pNext是不是指向NULL;如果不是,移到下一个结点;

2、取出数据,再判断pNext是不是NULL,如果不是,移到下一个结点;

3、找到最后一个结点的时候,把数据取出来!

代码实现:

void display_link(struct node *pHeader)
{
struct node *p = pHeader;

while(p->pNext != NULL)
{
p = p->pNext; //跳过头结点;
printf("%d\n",p->data);
}

}

③中间插入:

要求:在链表里面,找到一个数据,然后呢,将待插入的结点插入到这个数据结点的后面。

伪代码:

1、遍历链表,找到相应的数据所在结点;

2、将待插入的结点插入到第一步寻找到的结点的后面;

(1)将待插入的结点的pNext指向第一步结点的后面的那个结点;

(2)将第一步寻找到的结点指向待插入的结点;

代码:

void insert_mid(struct node *pHeader,int num,struct node *new)
{

int flag = 0;

struct node *p = pHeader;

//1、遍历链表,找到相应的数据所在结点;

while(p->pNext != NULL)
{

p = p->pNext;//跳过头结点以及移到下一个结点;

if(p->data == num)//判断当前结点的数据域是否是查找的数据;
{
//1、将待插入的结点的pNext指向第一步结点的后面的那个结点;

new->pNext = p->pNext;

p->pNext = new;

flag = 1;

break;
}
}

//防止在链表里面没有找到相应的数据;

if(flag == 0)
{
printf("insert_mid error!\n");
}
}

④删除:

链表是用来存储数据的!既然是存储数据,有存肯定有取!有取肯定有删除。

伪代码:

1、遍历链表,找到要删除的结点:Node2;

2、将Node2前面的一个结点Node1指向Node2后面的那个结点Node3;

3、将Node2所分配的空间释放;

代码:
int delete_node(struct node *pHeader,int num)
{
struct node *p = pHeader;
struct node *prev = NULL;

//1、遍历链表,找到要删除的结点:Node2;

while(p->pNext != NULL)
{
//prev保存的是p前面一个结点的地址;

prev = p;

p = p->pNext;//跳过头结点或者移到下一个结点;

if(p->data == num)
{
if(NULL == p->pNext)
{
//2、将Node2前面的一个结点Node1指向Node2后面的那个结点Node3;

prev->pNext = NULL;

//3、将Node2所分配的空间释放;

free(p);

p = NULL;

return 0;
}
else
{
prev->pNext = p->pNext;//p结点的前一个结点指向p的下一个结点;

free(p);

p = NULL;

return 0;
}
}
}
printf("delete_node error!\n");
}
⑤逆序:
原来的顺序:     头结点-->Node1-->Node2-->Node3-->NULL;
逆序之后的顺序:    NULL <-- Node1<--Node2<--Node3<--头结点;

伪代码:【目前只有三个结点】
1、判断该链表是否只有头结点或者只有一个有效结点;【参数入口检查】
2、将Node2指向Node1,将Node3指向Node2;
3、将Node1的pNext指向NULL;
4、将头结点的pNext指向Node3;

代码:

int reverse_link(struct node *pHeader)
{
//1、判断该链表是否只有头结点或者只有一个有效结点;【参数入口检查】

if(pHeader->pNext == NULL || pHeader->pNext->pNext == NULL)
{
printf("reverse_link error!\n");

return -1;
}

//说明至少有两个有效结点;

struct node *ptr1 = pHeader->pNext;//ptr1指向Node1;

struct node *ptr2 = ptr1->pNext;//ptr2指向Node2;

struct node *ptr3 = ptr2->pNext;//ptr3指向Node3;

//因为至少有两个有效结点,所以要判断Node2的下一个结点是否为NULL

//根据上面的定义知道Node2的pNext是用ptr3来保存的,所以要判断ptr3是否为NULL;

while( ptr3 != NULL)
{
ptr2->pNext = ptr1;//Node2指向Node1;

ptr1 = ptr2;

ptr2 = ptr3;

ptr3 = ptr3->pNext;
}
ptr2->pNext = ptr1;//Node3指向Node2

//3、将Node1的pNext指向NULL;

pHeader->pNext->pNext = NULL;//目前pHeader->pNext指向的是Node1;

//4、将头结点的pNext指向Node3;

pHeader->pNext = ptr2;//因为此时ptr2指向的是Node3;
}

★头结点和头指针:

链表分为两类:带头结点的和不带头结点的;

不带头结点的:头指针指向的是第一个有效结点的,保存的是第一个有效节点的地址;

带头结点的: 头指针指向的是头结点,保存的是头结点的地址;头结点里面可以不存储数据只存储第一个有效结点的地址,当然也可以存储比如链表的结点个数。

链表里面:不一定要有头结点,但是一定要有头指针。