struct single_list
{
int data; //数据成员可以是多个不同类型的数据
struct note *next; //指向下一个地址的指针
};
单向链表结构图示:(以下示意图均摘自来源: http://blog.youkuaiyun.com/fisherwan/ar)
在单向链表中,头指针表示第一个节点的存储位置,而最后一个节点没有直接后继,所以尾指针的next指向NULL。单向链表中所有节点只有指向下一个节点的指针,即知道它的后驱,而不知道它的前驱。所以单向链表中的各种操作都需要在知道头指针的前提下进行,因此头指针非常重要。
单向链表的初始化:(图示中没有头结点)
初学者容易把头指针和头结点搞错,所在在这里介绍一下这两者的区别:
头指针表示一个链表起始位置,指向链表的第一个节点。而在有时创建单链表时,会在单链表的第一个节点之前设置一个节点,这个节点称为头结点。头结点的数据域可以不存储任何信息,但指针域指向第一个节点(头结点之后的节点,即第一个有数据的节点),此时,单链表的头指针指向头结点。
总结:头指针必须存在,且指向第一个节点。头结点可有可无,且不存储数据信息,如果有头结点,头指针就指向头结点,若没有头结点,头指针就指向有数据的第一个节点。(注意:这里所说的头结点和第一个节点并不是表示同一个节点)
单向链表的创建:
单向链表的创建就是在初始化后的第一个节点后面不断插入新的节点,且刚插入进来的新的节点的指针域指向NULL(即此时插入的新节点为链表尾)。
单向链表的插入:(图示为在中间插入)
单向链表的插入相较顺序表的插入效率要高很多,只需要修改指针的指向即可。可将单向链表的插入过程分为三种情况:
(1)在链表头部插入:(新插入的节点变成链表的第一个节点)
①将新节点的指针指向链表第一个节点:pnew->next = head->next;
②再把头指针指向新的节点:head = pnew;
(2)在链表中间插入:(在ptemp节点后插入)
①新插入节点的next即为原来temp的next:pnew>next = ptemp->next;
②原来ptemp节点的next变为新插入的节点:ptemp->next = pnew;、
以上①②的顺序不能变
(3)在链表尾部插入(遍历链表到尾部节点)
①新插入节点的next为NULL:pnew->next = NULL;
②原来尾部节点的next为新插入的节点:ptail->next = pnew;
单向链表的删除:(图示为在中间删除)
同样,单向链表的删除相较顺序表的删除效率要高很多,只需要修改指针的指向即可。可将单向链表的删除过程分为三种情况:
(1)在链表头部删除(头指针指向第二个节点)
①将头指针用一个中间变量代替:temp = head;
②头指针指向第二个节点:head = head->next;
③释放掉temp的内存空间:free(temp);
(2)在链表的中间删除(如图示,找到要删除节点的前驱)
①pdelete = ptemp->next;
②ptemp->next = pdelete->next;
③free(pdelete)
(3)在链表的尾部删除(倒数第二个节点直接指向NULL,释放原尾部节点的内存空间)
①ptr->next = ptail;
②ptr->next = NULL;
③free(ptail);
基于篇幅问题,单向链表的排序和清空操作就不再一一讲解,在下面程序代码中已有明确的注释信息。
单向链表的C语言实现:(codeblocks完美运行)
#include <stdlib.h>
#include <stdio.h>
typedef struct link_list
{
int date;
int length;
struct link_list *next;
}list;
//节点创建函数
list *creat_node()
{
//创建头节点
list *newnode = (list *)malloc(sizeof(struct link_list));
if(newnode == NULL)
{
printf("creat node fail\n");
}
else
{
newnode->next=NULL;
return newnode;
}
}
//插入数据到链表中
int insert_node(list *head,int date)
{
//创建节点
list *newnode = creat_node();
newnode->date = date;
//判断头节点是否为NULL
if(head != NULL)
{
//遍历头节点,中间变量P
list *p = head;
//遍历头节点到,最后一个数据
while(p->next != NULL )
{
//错误,这样会改变头节的位置,必须使用中间变量进行变量
// head = head->next;
p = p->next;
}
//把最后一个节点赋新的节点过去
p->next = newnode;
p->length++;
return 1;
}
else
{
printf("head is NULL\n");
return 0;
}
}
void print_list(list * head)
{
list *ptr = head->next;
if(head->next == NULL)
{
printf("链表为空!\n");
return ;
}
while(ptr != NULL)
{
printf("%d ",ptr->date);//先打印后遍历
ptr = ptr->next;
}
printf("\n");
}
//链表反转或排序后链表,因为没有头结点,所以用下面这种打印方法
void print_list1(list * head)
{
list *ptr = head;
if(head->next == NULL)
{
printf("链表为空!\n");
return ;
}
while(ptr->next != NULL)
{
printf("%d ",ptr->date);//先打印后遍历
ptr = ptr->next;
}
printf("\n");
}
int delete_node(list *head)
{
list *p = head;
list *q = head->next;
int i,j=0;
if(head->next == NULL)
{
printf("链表为空,无法进行删除操作!\n");
return 0;
}
else
{
printf("请输入你要删除的元素的序号(下标从1开始):");
scanf("%d",&i);
while(p != NULL && j < (i-1) )
{
p = p->next;
q = q->next;
j++;
}
if(q->next != NULL && j > (i-1) )
{
printf("无法找到要查找的元素编号!\n");
return 0;
}
else
{
int x;
p->next = q->next;
x = q->date;
free(q);
return x;
}
}
}
int update_list(list *head,int olddate,int newdate)
{
if(head == NULL)
{
printf("链表为空!\n");
return 0;
}
list *ptr = head;
while(ptr->next != NULL)
{
if(ptr->next->date == olddate)
{
ptr->next->date = newdate;
return 1;
}
ptr = ptr->next;
}
return 0;
}
int seek_list(list *head,int val)
{
if(head == NULL)
{
printf("链表为空!\n");
return 0;
}
int i=0;
list *ptr = head;
while (ptr->next != NULL)
{
i++;
if(ptr->next->date == val)
{
printf("找到该元素%d,位置序号为%d\n",val,i);
return 1;
}
ptr = ptr->next;
}
printf("没有找到该元素%d,请按任意键返回主菜单。\n",val);
return 0;
}
list* reverse_list(list *head)
{
if(head == NULL || head->next == NULL)
{
printf("链表为空!\n");
return NULL;
}
list *p,*q,*r;
p = head;
q = head->next;
head->next = NULL;
while(q != NULL)
{
r = q->next;
q->next = p;
p = q;
q = r;
}
//此时p为头指针
head = p;
return head;
}
//插入排序
list * sort_list(list *head)
{
list *first,*t,*p,*q;
first = head->next;
head->next = NULL;
while(first != NULL)
{
for(t = first,q = head ; q!=NULL && (q->date < t->date) ; p = q,q = q->next);
first = first->next;
if(q == head)
head = t;
else
p->next = t;
t->next = q;
}
return head;
}
//选择排序
list * sort_list1(list *head)
{
list *temp,*p,*p1,*p2;
p1 = (list*)malloc(sizeof(list));
p1->next = head;
head = p1;
for(temp = NULL;temp != head;temp = p)
{
for(p = p1 = head;p1->next->next != temp;p1 =p1->next)
{
if(p1->next->date > p1->next->next->date)
{
p2 = p1->next->next;
p1->next->next = p2->next;
p2->next = p1->next;
p1->next = p2;
p = p1->next->next;
}
}
}
p1 = head;
head = head->next;
free(p1);
p1 = NULL;
return head;
}
//冒泡排序
list *sort_list2(list* head)
{
if(head == NULL)
{
printf("链表为空!\n");
return NULL;
}
list * p =head->next;
list * p_pre = p;
int flag = 0;//标志位,跳过已排序好的部分,提高冒泡排序法的效率
while(p_pre->next != NULL)//外层循环
{
int temp = p_pre->date;
p = p->next;
while(p != NULL)//内层循环
{
if(temp <= p->date)//升序排序,如果前面的小于后面的,退出本次循环
{
p = p->next;//内层循环遍历
continue;
}
else//否则交换他们的数据
{
int t = p->date;
p->date = p_pre->date;
p_pre->date = t;
p = p->next;//内层循环遍历
flag = 1;//排序后flag标志位置1,判断后面的比较
}
if(flag == 0)//这是用来判断本来就已经是排序好的顺序,直接返回头指针
{
return head;
}
}
p_pre = p_pre->next;//外层循环遍历
p = p_pre;//p又回到之前的位置进行下一次循环
}
return head;
}
void menu()
{
system("cls");//清屏
printf("\t\t|=============================================|\t\t\n");
printf("\t\t|==================単链表操作=================|\t\t\n");
printf("\t\t|==================1.插入元素=================|\t\t\n");
printf("\t\t|==================2.打印链表=================|\t\t\n");
printf("\t\t|==================3.删除元素=================|\t\t\n");
printf("\t\t|==================4.修改链表=================|\t\t\n");
printf("\t\t|==================5.查找元素=================|\t\t\n");
printf("\t\t|==================6.倒叙链表=================|\t\t\n");
printf("\t\t|==================7.排序链表=================|\t\t\n");
printf("\t\t|=请输入对应的数字执行对应的功能!(输入0退出)=|\t\t\n");
printf("\t\t|====== 作者:RXX 时间:2017/07/04 ======|\t\t\n");
printf("\t\t|=============================================|\t\t\n");
}
int main()
{
//创建头节点
list *head = creat_node();
int num=0;
int date=0;
menu();
//插入节点
scanf("%d",&num);
while(num)
{
//printf("input i inset list |\n");
switch(num)
{
case 1:
{
printf("请输入要插入的元素:");
scanf("%d",&date);
insert_node(head,date);
printf("插入节点成功,请按任意键返回主菜单。\n");
break;
}
case 2:
{
//head = creat_node();
printf("打印链表:");
print_list(head);
printf("打印链表成功,请按任意键返回主菜单。\n");
break;
}
case 3:
{
printf("原有链表:\n");
print_list(head);
printf("删除操作!");
int n = delete_node(head);
printf("删除的元素为%d.\n",n);
printf("删除后的链表:\n");
print_list(head);
printf("删除元素成功,请按任意键返回主菜单。\n");
break;
}
case 4:
{
int olddate,newdate;
printf("原有链表:\n");
print_list(head);
printf("修改操作!");
printf("请属于要修改的元素:");
scanf("%d",&olddate);
printf("修改为:");
scanf("%d",&newdate);
if(update_list(head,olddate,newdate))
printf("修改元素成功,请按任意键返回主菜单。\n");
else
printf("没有找到要修改的元素%d\n",olddate);
break;
}
case 5:
{
int val,i;
printf("查找操作!");
printf("请属于要查找的元素:");
scanf("%d",&val);
seek_list(head,val);
break;
}
case 6:
{
list *p = reverse_list(head);
printf("反转后的链表:\n");
print_list1(p);
break;
}
case 7:
{
//插入和选择排序的打印需要调用print_list1函数
//list *p = sort_list(head);//插入排序
//list *p = sort_list1(head);//选择排序
list *p = sort_list2(head);//冒泡排序
printf("排序后的链表:\n");
print_list(p);
break;
}
}
getch();//读取任意键
menu();
scanf("%d",&num);
//清空缓存区
//while(getchar()!='\n');
}
printf("\t\t|=============感谢使用!再见!=============|\t\t\n");
return 0;
}
运行界面: