链表的定义:链表是一种物理存储上非连续,数据元素的逻辑顺序通过链表中的指针链接次序,实现的一种线性存储结构。
链表的特点:链表由一系列节点(链表中每一个元素称为节点)组成,节点在运行时动态生成(malloc),每个节点包括两个部分:
一个是存储数据元素的数据域
另一个是存储下一个节点的地址的指针域
一.单向链表的操作
1.链表的结构
♦ 链表由一个个节点构成,每个节点一般采用结构体的形式组织。
比如:typedef struct stu
{
int num;
float score;
struct stu *next;
}STU;
♦ 链表节点分为两个域
数据域:存放各种实际的数据,如:num、score等
指针域:存放下一节点的首地址
♦ 链表的一般结构如下:
♦ 链表的特点:
• 链表作为一个线性存储结构,链表的使用比数组更加灵活(数组都是在静态存储区中定义的数组)。
• 构造一个链表时,不一定在程序中指定链表的长度(节点个数),可以在程序的运行过程中动态的生成一个链表。
• 链表使用完后可以通过调用free释放节点的方式完成对整个链表空间的释放。
2.链表的创建
构建算法:首先申请一个链表节点,然后为该节点成员赋值,最后将链表节点添加到链表中
① 添加到链表尾部:顺序创建,新加入的节点放在链表尾部
♦ 当链表为空时,将链表头直接指向待添加节点(该节点成为了链表的第一个节点)
♦ 链表不为空时,首先遍历链表找到链表尾节点,然后将待添加节点挂接到尾节点上
STU* link_create_end(STU *head,STU *p_new) { STU *p_mov = head; if(head==NULL)//当第一次加入链表为空时,head执行p_new { head = p_new; p_new->next = NULL; } else//第二次及以后加入链表 { while(p_mov->next!=NULL) { p_mov = p_mov->next;//找到原有链表的最后一个节点 } p_mov->next = p_new;//将新申请的节点加入链表 p_new->next = NULL; } return head; }
②添加到链表头部:逆序创建,新加入的节点放在链表头部
♦ 当链表为空时,将链表头直接指向待添加节点(该节点成为了链表的第一个节点)
♦ 链表不为空时,首先将之前的第一个链表节点挂接到新插入的节点上,然后将链表头指向新插入的节点
STU *link_create_head(STU *head,STU *p_new) { if(head==NULL) { head = p_new; p_new->next = NULL; } else { p_new->next = head; head = p_new; } return head; }
3.链表的遍历:遍历输出链表所有节点
♦ 得到链表第一个节点的地址,即head的值
♦ 设一个临时指针变量p_mov,指向第一个节点head,即可获取p_mov所指节点的信息
♦ 使p_mov后移一个节点,即可访问下一节点,直到链表的尾节点(注意结尾判断条件)
void link_print(STU *head) { printf("num name score "); while(head!=NULL) { printf("%d %s %.2f ",head->num,head->name,head->score); head = head->next; } }
4.链表的查找:按照指定关键字查找节点
♦ 得到链表第一个节点的地址,即head的值
♦ 设一个临时指针变量p_mov,指向第一个节点head,即可获取p_mov所指节点的信息
♦ 比较是否是要查找的节点
• 是,则返回相应节点地址,停止查找
• 不是,使p_mov后移一个节点,即可访问下一节点,直到链表的尾节点(注意结尾判断条件),最后若找不到返回NULL
STU *search_link_num(STU *head,int num) //按学号查找 { STU *p_mov = head; while(p_mov!=NULL) { if(p_mov->num == num) //找到了 { return p_mov; } p_mov = p_mov->next; } return NULL; //没有找到 }
STU *search_link_name(STU *head,char *name) //按姓名查找 { STU *p_mov = head; while(p_mov!=NULL) { if(strcmp(p_mov->name,name) == 0) { return p_mov; //找到了 } p_mov = p_mov->next; //没有找到 } return NULL; }
5.链表的有序插入:在一个链表的指定位置插入节点,要求链表本身必须是已经按某种规律排好序的。
①.原链表为空:只需使head指向被插节点即可。
②.在第一个节点之前插入:使head指向被插节点,被插节点的next指向原来的第一节点。
STU *link_insert_head(STU *head,STU *p_new) //在表头插入 { if(head==NULL) //链表为空 { head = p_new; p_new->next = NULL; } else { p_new->next = head; //新来的节点作为头节点 head = p_new; } return head; }
③.在中间位置插入:使插入位置的前一节点的next指向被插节点,被插节点的next指向插入位置的后一节点。
STU *link_insert_order(STU *head,STU *p_new) //按顺序插入 { STU *pf = head,*pb = head; if(head==NULL)// 链表为空链表 { head = p_new; p_new->next = NULL; } while((pb->num<p_new->num) && (pb->next!=NULL))//循环找 { pf = pb; pb = pb->next; } if(pb->num >= p_new->num)//找到一个节点的num比新来的节点num大,插在pb的前面 { if(pb == head)//找到的节点是头节点,插在最前面 { p_new->next = head; head = p_new; } else { pf->next = p_new; p_new->next = pb; } } else//没有找到pb的num比p_new->num大的节点,插在最后 { pb->next = p_new; p_new->next = NULL; } return head; }
④.在表末尾插入:是链表尾节点next指向被插节点,被插节点next指向NULL。
STU *link_insert_end(STU *head,STU *p_new) //在尾部插入 { STU *p_mov = head; if(head==NULL) //没有节点 { head = p_new; p_new->next = NULL; } else { while(p_mov->next!=NULL) //找到链表最后一个节点 { p_mov = p_mov->next; } p_mov->next = p_new; //最后一个节点指向新插入的节点 p_new->next = NULL; } return head; }
6.链表的删除:删除是将某一节点从链中摘除,并释放相应的空间。
♦ 删除的第一步是找到要删除的节点,同查找算法,若找不到或链表为空,提示未找到
♦ 找到后根据情况删除此节点
①.被删节点是第一个节点:只需使head指向第二个节点即可。
②.被删节点不是第一个节点:使被删节点的前一节点指向被删节点的后一节点即可。
STU *delete_link_num(STU *head,int num) //按学号删除 { STU *pf = head,*pb = head; if(pb==NULL)//链表为空,不用删 { printf("链表为空,没有您要找的结点 "); return ; } while((pb->num != num) && (pb->next!=NULL))//循环找,要删除的节点 { pf = pb; pb = pb->next; } if(pb->num == num)//找到了一个节点的num和num相同 { if(pb == head)//要删除的节点是头节点 { head = pb->next; } else { pf->next = pb->next; } free(pb); } else//没有找到 { printf("没有您要找的结点 "); } return head; }
STU *delete_link_name(STU *head,char *name) //按姓名删除 { STU *pb = head,*pf = head; if(pb == NULL)//链表为空,不用删 { printf("链表为空,没有您要找的结点 "); return ; } while((strcmp(pb->name,name)!=0) && (pb->next!=NULL))//循环找,要删除的节点 { pf = pb; pb = pb->next; } if(strcmp(pb->name,name)==0)//找到了一个节点的name和name相同 { if(pb == head)//要删除的节点是头节点 { head = pb->next; } else { pf->next = pb->next; } free(pb); } else//没有找到 { printf("没有您要找的结点 "); } return head; }
7.链表的释放
♦ 同遍历链表类似,区别在于p_mov每指向某个节点后都将该节点释放
♦ 释放前要先保存下一个节点,释放后备份恢复给p_mov,否则释放了当前节点,下一个节点的地址就将丢失
♦ 依次将所有节点释放后,最后返回NULL(标示释放完毕)
STU *free_link(STU *head) { STU *pb = head; while(head!=NULL) { pb = head; head = head->next; free(pb); } return NULL; }
8.链表排序:当链表本身是无序的时候,我们需要对链表的所有数据进行排序,算法同冒泡法、选择法。
STU *link_order(STU *head) //排序 { STU *pf = head,*pb,temp; if(head == NULL) { printf("链表为空 "); return ; } if(head->next == NULL) { printf("只有一个结点,不用排序 "); return ; } while(pf->next!=NULL)//以pf指向的节点为基准节点, { pb = pf->next;//pb从基准元素的下个元素开始 while(pb!=NULL) { if(pf->num>pb->num) { temp = *pf; *pf = *pb; *pb = temp; temp.next = pf->next; pf->next = pb->next; pb->next = temp.next; } pb = pb->next; } pf = pf->next; } return head; }
9.链表逆序:将链表的所有节点逆序存放,原来的头结点变为尾节点,原来的尾节点变为头结点。
STU *link_reverse(STU *head) //链表的逆序 { STU *pf = head,*pb,*ps; if(head == NULL) return NULL; pb = pf->next; while(pb!=NULL) { ps = pb->next; pb->next = pf; pf = pb; pb = ps; } head->next = NULL; head = pf; return head; }
二.双向链表的操作
1.双向链表的结构
typedef struct student
{
int num;
char name[30];
struct student *next; //指向后一个节点的指针域
struct student *prior; //指向前一个节点的指针域
}STU;
2.双向链表的插入
STU *insert_link_oder(STU *head,STU *pnew) //双向链表的顺序插入 { STU *pb = head,*pf = head; if(head == NULL) { head = pnew; head->next = head; head->prior = head; }else { while((pb->num < pnew->num)&&(pb->next != head)) { pf = pb; pb = pb->next; } if(pb->num >= pnew->num) { if(pb == head) { head->prior->next = pnew; pnew->prior = head->prior; pnew->next = head; head->prior = pnew; head = pnew; } else { pf->next = pnew; pnew->prior = pf; pnew->next = pb; pb->prior = pnew; } } else { pb->next = pnew; pnew->prior = pb; pnew->next = head; head->prior = pnew; } } return head; }
3.双向链表的删除
STU *delete_link_num(STU *head,int num) //双向链表按学号删除 { STU *pf = head,*pb = head; if(head == NULL) return NULL; while(pb->next != head) { if(pb->num == num) { if(pb == head) { pb->prior->next = pb->next; pb->next->prior = pb->prior; head = pb->next; } else { pf->next = pb->next; pb->next->prior = pf; } free(pb); break; } pf = pb; pb = pb->next; } if(pb->num == num) { pf->next = head; head->prior = pf; free(pb); if(pb == head) head = NULL; } return head; }
4.双向链表排序
STU *oder_link(STU *head) //双向链表排序 { STU *pf = head,*pb = head,temp; if(head == NULL) return NULL; while(pf->next != head) { pb = pf->next; while(pb != head) { if(pf->num < pb->num) { /* 交换 */ temp = *pf; *pf = *pb; *pb = temp; temp.next = pf->next; temp.prior = pf->prior; pf->next = pb->next; pf->prior = pb->prior; pb->next = temp.next; pb->prior = temp.prior; } pb = pb->next; } pf = pf->next; } return head; }