单链表的定义
线性表的链式存储又称为单链表,它是指通过任意一组的存储单元来存储线性表中的数据元素。 在单链表中,每个节点包含一个指向链表下一节点的指针。链表最后一个节点的指针字段的值为NULL,提示链表后面不再有其它节点。它是非随机存取的存储结构,操作时只能从表头开始遍历。
通常用 “头指针” 标识一个单链表,头指针为“NULL“ 表示空表,为了操作上的方便,在单链表的第一个节点前附加一个节点,称头节点 ,引入头节点的两个优点:
1)由于开始节点的位置被存放被存放在头节点的指针域,所以再链表第一个位置的操作和在表的其他位置上操作一致,无需特殊对待
2)无论链表是否空,其头指针是指向头节点的非空指针(空表中头节点的指针域为空),因此空表的和非空表的处理也就统一了
因此以下讨论基于带有头节点的单链表
采用数据结构:
typedef struct node{
ElemType data;
struct node * next;
}node;
1.创建
1)头插法
node *creat(){
node *head=new node,*p,*pre;
head->next =NULL; //original
pre=head;
int x;
scanf("%d",&x);
while(x!=-1){
p=new node;
p->data =x;
p->next =pre->next ; //每个节点插入O(1),总O(n)
pre->next =p;
scanf("%d",&x);
}
return head;
}
头插法虽简单,但生成链表中节点的次序与输入顺序不一致,若希望两者次序一致,可采用头插法。
2)尾插法
node *creat(){
node *head=new node;
head->next =NULL;
int x;
cin>>x;
node *pre=head,*p;
while(x!=-1){
p=new node;
p->data =x;
p->next=NULL;
pre->next =p;
pre=p;
cin>>x;
}
return head;
}
2.查找
1)按序号查找
2)按值查找
这两种都比较简单,直接遍历链表搜索,符合条件时输出,此不赘述
3.删除节点
1)按序号删除
关键点:遍历查找单链表中待删除节点的前驱
node* Del_node_id( node *head,int i){
node *p=head,*q;
int count=0;
while(p->next !=NULL){
count++;
q=p->next; // 找q前驱
if(count==i){
p->next =q->next;
delete q;
return head;
}
p=p->next;
}
//i 不符合实际要求
printf("你的删除元素位置有错!\n");
return head;
}
2)按元素值删除
node *Del_node_value(node *head,int value){
node *p=head,*q;
while(p->next !=NULL){
q=p->next ; //找q前驱节点
if(p->next->data==value ){
p->next =q->next ;
delete q;
return head;
}
p=p->next ; //指针后移
}
printf("此链表没有值为value的节点!\n");
return head;
}
删除过程注意不要断链即可
那么问题来了:如何删除单链表中的重复节点(保留第一个重复节点)
思路1:两层循环,暴力枚举删除,时间复杂度O(n^2),空房间复杂度O(1)
void Del_linklist(node * H){
node *p,*q,*r;
p=H->next;
if(p!=NULL) //非空链表
while(p->next){ //外层,需大于1个节点
q=p-next;
while(q){ //内层,p的下一个节点开始遍历
if(q->next->data==p->data){ //删除符合条件的节点
r=q->next;
q->next=r->next;
delete r;
}
q=q->next;
}
p=p->next;
}
}
思路2:改进第一种做法,采用空间换时间,使用辅助数组记录链表已出现的节点,从而只对链表进行一边扫描,边遍历边标记,符合删除条件,则delete。时间复杂度O(n),空房间复杂度O(n)
node *delList(node *head){
node *r,*p=head->next;
r=p;
if(p==NULL) return NULL;
fill(vis,vis+1000,false);
while(p!=NULL){
if(vis[p->data]==true){
r->next =p->next ;
delete p;
p=r->next ;
}
else{
vis[p->data]=1;
r=p;
p=p->next ;
}
}
return head;
}
类似问题:如何删除链表中重复元素(重复元素全部删除)
提示:空间换时间,采用Hash[ ]散列,一遍扫描标记,一遍扫描删除,只需两次遍历。时间复杂度O(n),空房间复杂度O(n)。
node *del(node *head){
fill(hash,hash+maxn,0);
node *r,*p=head->next;
r=head;
if(p==NULL) return NULL;
while(p){ //一遍扫描标记
hash[p->data]++;
//cout<<p->data <<hash[p->data]<<endl;
p=p->next ;
}
//开始开始喽
p=r-next;
while(p!=NULL){ //一遍扫描删除
if(hash[p->data]>1){
r->next =p->next ;
delete p;
p=r->next ;
}else{
r=p;
p=p->next ;
}
}
return head;
}
4.插入节点
关键点:找到待插入节点位置的前驱
node* Insert_linklist(node *head,int i,int value){
node *p=head,*q;
int count=0;
while(p->next !=NULL){
count++;
if(count==i) //找待插入节点的前驱指针 p
break;
p=p->next;
} //开始插入
q=new node;
q->data =value;
q->next =p->next ;
p->next =q;
return head;
}
5.逆置链表
关键点:不断遍历链表,将后一个节点的next指向前一个节点
最后头节点挂到原链表的尾部
node* Transpose_linklist(node *H){
node*p,*q,*pr;
p=H->next ;
H->next =NULL; //摘除头节点
q=NULL;
while(p){
pr=p->next;
p->next=q; //后一个节点的next指向前一个节点
q=p; //尾指针为空
p=pr; //指针后移
}
H->next =q; //头节点挂到原链表的尾部
printf("转置后链表:\n");
}
6.打印链表
void output_linklist(node* H){
node *p=H->next ;
while(p){
printf("%2d",p->data );
p=p->next ;
}
}
7.代码
#include<cstdio>
#define ElemType int
struct node{
ElemType data;
node * next;
};
//尾插法创建单链表
node *creat1( ){
node*head,*pre,*p;
int x;
scanf("%d",&x);
head=new node;
head->next=NULL;
pre=head;
while(x!=-1){
p=new node;
p->data=x;
p->next=NULL;
pre->next=p;
pre=p;
scanf("%d",&x);
}
return head;
}
//头插法创建单链表
/*
node *creat2(){
* node *head=new node,*p,*pre;
* head->next =NULL; //original
* pre=head;
* int x;
* scanf("%d",&x);
* while(x!=-1){
* p=new node;
* p->data =x;
* p->next =pre->next ; //每个节点插入O(1),总O(n)
* pre->next =p;
* scanf("%d",&x);
* }
* return head;
}*
*/
//转置单链表
node* zhuanzhi_linklist(node *H)
{
node*p,*q,*pr;
p=H->next ;
H->next =NULL; //摘除头节点
q=NULL;
while(p)
{
pr=p->next;
p->next=q;
q=p; //尾指针为空
p=pr; //指针后移
}
H->next =q;
printf("转置后链表:\n");
}
//删除单链表中重复的节点,时间复杂度o(n^2)
//hash[]后删除效率高
void Del_linklist(node * H){
node *p,*q,*r;
p=H->next;
if(p!=NULL)
while(p->next) {
q=p;
while(q->next)
{
if(q->next->data==p->data)
{
r=q->next;
q->next=r->next;
delete r;
}
q=q->next;
}
p=p->next;
}
}
//删除某位置节点
node* Del_node_id( node *head,int i){
node *p=head,*q;
int count=0;
while(p->next !=NULL){
count++;
q=p->next; // 找q前驱
if(count==i){
p->next =q->next;
delete q;
return head;
}
p=p->next;
}
//i不符合实际要求
printf("你的删除元素位置有错!\n");
return head;
}
//删除值为value的节点
node *Del_node_value(node *head,int value){
node *p=head,*q;
while(p->next !=NULL){
q=p->next ;
if(p->next->data==value ){
p->next =q->next ;
delete q;
return head;
}
p=p->next ;
}
printf("此链表没有值为value的节点!\n");
return head;
}
//添加 n 个节点
node *Add_linklist(node *head){
node *p=head,*q;
while(p->next!=NULL){ //此处的 p 应指向尾节点,方便添加新的节点
p=p->next ;
}
int x;
scanf("%d",&x);
while(x!=-1){
q=new node;
q->data =x;
q->next =NULL;
p->next =q;
p=q;
scanf("%d", &x );
}
return head;
}
//插入节点
node* Insert_linklist(node *head,int i,int value){
node *p=head,*q;
int count=0;
while(p->next !=NULL){
count++;
if(count==i) //找带插入节点位置的前驱指针 p
break;
p=p->next;
}
q=new node;
q->data =value;
q->next =p->next ;
p->next =q;
return head;
}
//打印节点
void output_linklist(node* H)
{
node *p=H->next ;
while(p)
{
printf("%2d",p->data );
p=p->next ;
}
}
int main(){
node*head=creat1();
printf("打印链表:\n");
output_linklist(head);
printf("\n");
Transpose_linklist(head);
output_linklist(head);
printf("\n");
Del_linklist(head);
printf("删除重复节点,保留第一个:\n");
output_linklist(head);
printf("\n");
printf("输入尾部要添加节点,-1表示结束:\n");
head=Add_linklist(head);
output_linklist(head);
printf("\n");
int i,value;
printf("输入要插入的元素位置及元素值:\n");
scanf("%d%d",&i,&value);
Insert_linklist(head, i, value);
output_linklist(head);
printf("\n");
printf("please input del posttion:\n");
int j;
scanf("%d",&j);
Del_node_id(head, j);
output_linklist(head);
printf("\nplease input del value:\n");
scanf("%d",&value);
Del_node_value(head, value);
output_linklist(head);
return 0;
}