一、前言
内存存储方式
内存通常分为:Heap ,Stack ,Static/Global ,Code(Text),一部分存储所有需要被执行的指令,另一部分存储全局变量,栈存储所有有关函数调用执行的信息和所有局部变量,这三个部分的大小是固定的,最后的堆或空闲存储是不固定的。
二、插入
1)在头部插入一个节点
设置全局变量head时
#include <iostream>
using namespace std;
struct Node
{
int data;
struct Node* next;
};
struct Node* head;//全局变量
void Insert(int x)
{
Node* temp=(Node*)malloc(sizeof(struct Node));
temp->data=x;
temp->next=head;
head=temp;
}
void Print()
{
struct Node* temp=head;
while(temp!=NULL)
{
cout<<" "<<temp->data;
temp=temp->next;
}
cout<<endl;
}
int main()
{
head=NULL;//链表为空
int n,x;//元素的个数和元素
cin>>n;
for(int i=0;i<n;i++)
{
cin>>x;
Insert(x);
Print();//不带参数
}
}
如果不设置全局变量head,则代码修改为:
//Linked List:Inserting a node at biginning
#include <iostream>
using namespace std;
struct Node
{
int data;
struct Node* next;
};
Node* Insert(Node* head,int x)
{
Node* temp=(Node*)malloc(sizeof(struct Node));
temp->data=x;
temp->next=head;
head=temp;
return head;
}
void Print(Node* head)
{
while(head!=NULL)
{
cout<<" "<<head->data;
head=head->next;
}
cout<<endl;
}
int main()
{
Node* head=NULL;//链表为空
int n,x;//元素的个数和元素
cin>>n;
for(int i=0;i<n;i++)
{
cin>>x;
head=Insert(head,x);
Print(head);//不带参数
}
}
2)在任意位置插入一个节点
考虑到链表为空,或插入位置无效。
如果链表有3个元素,则可以在1、2、3、4位置上插入。
在n位置插入,就要把原来n位置的next指针赋值给新的x,新的x的地址赋值给原来该位置的指针
#include <iostream>
using namespace std;
struct Node
{
int data;
struct Node* next;
};
struct Node* head;
void Insert(int data,int n)
{
Node* temp1=new Node();
temp1->data=data;
temp1->next=NULL;
if(n==1)
{
temp1->next=head;
head=temp1;
return;
}
Node* temp2=head;
for(int i=0;i<n-2;i++)
{
temp2=temp2->next;
}
temp1->next=temp2->next;
temp2->next=temp1;
}
void Print()
{
struct Node* temp=head;
while(temp!=NULL)
{
cout<<" "<<temp->data;
temp=temp->next;
}
cout<<endl;
}
int main()
{
head=NULL;//empty list
Insert(2,1);//: 2
Insert(3,2);//: 2,3
Insert(4,1);//: 4,2,3
Insert(5,2);//: 4,5,2,3
Print();
}
三、删除
在任意位置删除一个节点
思路:修理连接,释放内存
#include <iostream>
using namespace std;
struct Node
{
int data;
Node* next;
};
struct Node* head;
void Insert(int data)//默认在头部插入
{
Node* temp=new Node();
temp->data=data;
temp->next=head;
head=temp;
}
void Print()
{
Node* temp=head;
while(temp!=NULL)
{
cout<<" "<<temp->data;
temp=temp->next;
}
cout<<endl;
}
void Delete(int n)
{
Node* temp=head;
if(n==1)
{
head=temp->next;
free(temp);
return;
}
for(int i=0;i<n-2;i++)
{
temp=temp->next;
}
Node* temp1=temp->next;
temp->next=temp1->next;
free(temp1);
}
int main()
{
head=NULL;
Insert(2);
Insert(3);
Insert(4);
Insert(5);//: 5 4 3 2
Print();
int n;
cin>>n;
Delete(n);
Print();
}
四、打印
用递归法打印一个链表(顺序、逆序)
tips:如果不用全局变量head时代码的改动?Insert插入函数会有一个返回值,我们把这个返回值赋值给主函数的局部变量head。
顺序打印的思路?首先我们判断递归是否要进行,也就是链表是否为空,我们令temp=head,如果temp等于NULL,就没有递归的必要了。
void Print(Node* temp)//一个参数 temp指针
{
temp=head;//把head的值赋给temp
if(temp==NULL)//如果链表为空,或打印到最后一个元素了,退出递归
{
cout<<endl;
return;
}
cout<<temp->data<<" ";//输出当前值
print(temp->next);//对下一个值调用递归
}
逆序打印如何实现呢?顺序时,先打印值,再进行递归。那如果我们先进行递归调用,递归结束后再打印值,运行后,我们就得到了链表逆序打印的结果。这样不会修改代码的其他内容,仅仅是逆序打印。过程就是,当我们调用第一个值的时候,我们会一直递归直到最后一个值,地址为NULL,就会返回,退出递归。这时候,我们就开始打印了,从最后一个数值开始。
递归在内存中如何执行呢?分配给程序执行的内存有两部分:栈和堆。 函数的所有详细信息(包括执行信息和局部变量),malloc函数或C++中的new函数分配的所有内存,都存储在栈中。函数每次调用都会分配对应的栈帧,即使调用函数本身,我们也需不停执行栈帧。函数返回后,这个函数完成,我们将逐渐释放栈帧。
我们需要知道,对于链表顺序打印时,迭代要比递归更有效率,因为递归进行很多次调用函数,隐式使用了内存,而对于逆序打印,递归是可以的。
五、反转链表
1)迭代方法
#include <iostream>
using namespace std;
struct Node
{
int data;
Node* next;
};
Node* head;
void Insert(int data)
{
Node* temp=new Node();
temp->data=data;
temp->next=NULL;
if(head==NULL)
{
head=temp;
return;
}
Node* temp1=head;
while(temp1->next!=NULL)
{
temp1=temp1->next;
}
temp1->next=temp;
}
void Print()
{
Node* temp=head;
while(temp!=NULL)
{
cout<<" "<<temp->data;
temp=temp->next;
}
cout<<endl;
}
Node* Reverse(Node * head)
{
Node *prev=NULL,*temp=head,*next;
while(temp!=NULL)
{
next=temp->next;
temp->next=prev;
prev=temp;
temp=next;
}
head=prev;
return head;
}
int main()
{
head=NULL;
Insert(2);
Insert(4);
Print();
head=Reverse(head);
Print();
}
2)递归方法
void Reverse(Node* temp)
{
if(temp->next==NULL)
{
head=temp;
return;
}
Node* p=temp->next;
p->next=temp;//p->next->next=p与前两行是一样的意思
temp->next=NULL;
}
六、双向链表
在双向链表中,每个节点将具有两个链接,一个链接下一个节点,另一个链接上一个节点
双向链表的优点和用处是什么?可以进行反向查询,仅需一个指针,可以找到上一个或下一个。缺点是,我们会为上一个节点指针使用额外的内存,重置链接时,也要重设比单链表更多的链接。
struct Node
{
int data;
Node* next;
Node* prev;
};
Node* head;//全局变量
创建新节点并插入
//新节点
Node* GetNewNode(int x)
{
Node* newNode=new Node();//类型是Node*
newNode->data=x;
newNode->next=NULL;
newNode->prev=NULL;
return newNode;
}
//或者写成
Node* GetNewNode(int x)
{
Node newNode; //类型是Node
newNode.data=x;
newNode.next=NULL;
newNode.prev=NULL;
return &newNode;//返回地址
}
//插入
void InsertAtHead(int x)
{
Node* newNode=GetNewNode(x);
if(head==NULL)
{
head=newNode;
return;
}
head->prev=newNode;
newNode->next=head;//把head和newNode建立双向链接
head=newNode;//最后修改一下head
}
打印
顺序打印和单链表是一样的,逆序打印就不需要递归或迭代了,利用链接的反向遍历就可以
void ReversePrint()
{
Node* temp=head;
if(temp==NULL)
{return;}
while(temp->next!=NULL)
{
temp=temp->next;//先顺序遍历到最后一个节点
}
while(temp!=NULL)
{
cout<<" "<<temp->data;//打印数据
temp=temp->prev;//反向遍历
}
}