链表
一.链表的概念
1.链表由若干个结点组成(每个结点代表一个元素),且结点在内存中的存储位置通常是不连续的。
2.链表的两个结点之间一般通过一个指针来从一个结点指向另一个结点,因此链表的结点一般由两部分构成,即数据域和指针域。数据域存放结点要存储的数据,指针域指向下一个结点的地址。这样就会产生从某个结点开始的,由指针连接的一条链式结构。
Struct node{
typename date; //数据域
node *next; //指针域
}
3.以链表是否存在头结点,又可以把链表分为带头结点的链表
和不带头结点的链表
。
(1)带头结点的链表
:头结点一般称为head
,其数据域data不存放任何内容,指针域next指向第一个数据域有内容的结点(第一个结点)。最后一个结点的next指针指向null,即空地址。如图所示为带头结点的链表:
(2)不带头结点的链表
:即从第一个结点作为链表开始,与上图类似,只是没有head。
二. 使用malloc 函数或者new 运算符为链表结点分配内存空间
2.1 malloc
malloc
函数是C语言中stdlib.h
头文件下用于申请动态内存的函数(C++11中可不需要头文件),其返回类型是申请的同变量类型的指针。如果申请失败则返回空指针NULL(失败一般发生在使用malloc申请较大的动态数组)。- malloc()函数返回的是
void*
类型,所以用的时候要进行强制类型转换,基本用法如下:typename *p=(typename *)malloc(sizeof(typename))
例子:
int型变量:int *L=(int *)malloc(sizeof(int));
node型结构体变量:node *p=(node *) malloc(sizeof(node))
2.2 new运算符
- new是C++中用来申请动态空间的运算符,其返回类型同样是申请的同变量类型的指针。如果申请失败(较大的动态数组),则会启动C++异常机制处理,而不是放回空指针NULL。
- 用法:
typeame* p = new typename
例子:
(1)int型变量:int *p=new int
(2)node型结构体变量:node *p =new node
2.3 内存泄漏
- 内存泄漏是指使用
malloc
和new
开辟出来的内存空间在使用过后没有释放,导致其在程序结束之前始终占据内存空间。所以为了防止内存泄露,使用完malloc
和new
开辟出来的空间后必须释放。 - free函数
(1)使用:free(p)
(2)效果:释放指针变量p所指向的内存空间,将指针变量p指向空地址NULL。由此可知,free函数执行之后,指针变量p本身并没有消失,只不过让它指向了空地址NULL,但是它原指向的内存是确实被释放了的。malloc函数与free函数必须成对出现,否则容易产生内存泄漏
- detele运算符
(1)使用:delete(p)
(2)效果:new运算符与delete运算符必须成对出现,否则会容易产生内存泄漏
三.动态链表的基本操作
3.1 创建链表
#include<iostream>
using namespace std;
struct node{ //链表结点
int data;
node* next;
};
node* create(int Array[])
{
node *p,*pre,*head; //pre保存当前结点的前驱结点,head为头结点
head =new node; //创建头结点
head->next=NULL; //头结点不需要数据域,指针域初始为NULL
pre= head; //记录pre为head
for(int i=0;i<5;i++){
p=new node; //新建结点
p->data=Array[i]; //数据域赋值
p->next=NULL; //新结点的指针域设为NULL
pre->next=p; //前驱结点的指针域为当前新建结点的地址
pre=p; //把pre设为p,作为下个结点的前驱结点
}
return head; //返回头指针
}
int main()
{
int Array[5]={5,3,6,1,2};
node *L=create(Array); //新建链表,放回的头指针head赋给L
L=L->next; //从第一个结点开始有数据域
while(L!=NULL){
cout<<L->data<<' '; //输出每个结点的数据域
L=L->next;
}
return 0;
}
3.2 查找元素
int search(node *head,int x){
int count=0; //计数器
node *p=head->next; //从第一个结点开始
while(p!=NULL){ //只要没有到链表末尾
if(p->data==x) count++;
p=p->next;
}
return count;
}
3.3 插入元素
1.在第3个位置插入元素4的意思是指在插入完成之后第3个位置的元素就是4,即把原先第3个位置开始的元素让出来给需要插入的数。
2.如图所示在第3个位置插入元素4,操作顺序必须先把新结点的指针域next指向后继结点6,之后才能把元素3所在结点的指针域指向新结点的地址。
3. 代码
void insert(node *head,int pos,int x){ //将x插入以head为头结点的链表的第pos个位置上
node *p=head;
for(int i=0;i<pos-1;i++){
p=p->next; //pos-1为了到插入位置的前一个结点
}
node *q=new node;
q->data=x;
q->next=p->next; //新结点的下一个结点指向原先插入位置的结点
p->next=q; //前一个位置的结点指向新的结点
}
3.4 删除元素
1.思想:删除元素是指删除链表上所有值为给定的数x,如图删除元素6
2.操作(看图理解)
(1)由指针变量p枚举结点,另一个指针变量pre表示p指向结点的前驱结点。
(2)当p所指结点的数据域恰好为x时,进行下面三个操作:
- 令pre所指结点的指针域next指向p所指结点的下一个结点。
- 释放p所指结点的内存空间
- 令p指向pre所指结点的下一个结点
3.代码
void del(node *head,int x){ //删除以head为头结点的链表中所有数据域为x的结点
node *p=head->next; //p从第一个结点开始枚举
node *pre=head; //pre始终保存p的前驱结点指针
while(p!=NULL){
if(p->data==x){ //数据域为x,说明要删除该结点
pre->next=p->next;
delete(p);
p=pre->next;
}
else{ //数据域不是x,pre和p都后移一位
pre=p;
p=p->next;
}
}
}