09.数据结构学习3.0
1、双向链表
(1)、定义:双向链表,又称为双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。
#define ELementType int
typedef struct node
{
ElementType data;
struct node *prior,*next;//设置前驱节点和后继节点
}Node;
struct DoubleLinkList
{
struct Node * head;
struct Node * tail;
int len;
};
typedef struct DoubleLinList DLlist;
(2)、双向链表的初始化:
int InitDLlist(DLlist *list)
{
list->head = NULL;//设置头指针为空
list->tail = NULL;//尾指针为空
list->len = 0;//设置初始化长度为0
return 0;
}
(3)、双向链表的插入
封装一个函数实现:申请一个新节点
Node * CreateNode(ElementType element)
{
Node *NewNode = (Node*)malloc(sizeof(Node));//动态申请一个新节点
if(NewNode == NULL)//判断申请是否成功
{
printf("CreateNode malloc error!\n");//申请失败打印错误信息,返回空指针
return NULL;
}
NewNode->prior = NULL;//初始化新节点的前驱和后继为空
NewNode->next = NULL;
NewNode->data = element;//新节点的数值为要插入的数值
return NewNode;//返回新节点
}
【1】、在尾部插入
void InsertTail(DLlist *list,ElementType element)
{
Node *NewNode = CreateNode(element);//申请新节点
if(NewNode == NULL)//判断新节点是否申请成功
{
printf("InsertTail malloc error!\n");
return ;
}
if(list->len == 0)//因为当链表为空时,尾指针没有next会报错,所以要判断链表是否为空
{
list->head = NewNode;//如果是第一个节点,则将所有的指针指向它
list->tail = MewNode;
}
else
{
list->tail->next = NewNode;//尾指针的后继指向新节点
NewNode->prior = list->tail;//新节点的前驱指向链表的尾部
list->tail = NewNode;//更新尾部节点
}
list->len++;//长度加一
}
【2】、在头部插入新节点
void InsertHead(DLlist *list,ElementType element)
{
Node *NewNode = CreateNode(element);//申请新节点
if(NewNode == NULL)//判断新节点是否申请成功
{
printf("InsertHead malloc error!\n");
return ;
}
if(list->len == 0)//因为当链表为空时,尾指针没有next会报错,所以要判断链表是否为空
{
list->head = NewNode;//如果是第一个节点,则将所有的指针指向它
list->tail = MewNode;
}
else
{
NewNode->next = list->head;
list->head->prior = NewNode;
list->head = NewNode;
}
list->len++;
}
(4)、删除节点
【1】、按位删除
考虑在不同位置删除的具体情况;
1、在头部进行删除:当链表长度为1,首尾相同,释放头节点,指针全部置空;
当链表长度不为1,释放头节点,头指针后移一位,新头节点前驱置为空;
2、在尾部进行删除:释放尾节点,尾指针前移一位;尾节点的后继指向空;
3、在链表中间进行删除,当前的前驱节点的后继指向当前节点的后继节点,当前节点的后继节点的前驱指向当前节点的前驱节点。
void RemoveByIndex(DLlist *list,int index)
{
if(index < 0 || index >= list->len)//判断删除位置是否合法
{
printf("Remove invalid place!\n");
return ;
}
if(index == 0)//判断有删除的位置是否为第一位
{
if(list->len == 1)//判断当前链表是否只有一个节点
{
free(list->head);//释放头节点
list->head = NULL;//当前链表已无任何节点,头指针和尾指针全部置空
list->tail = NULL;
list->len --;//链表的长度-1
return ;
}
Node *FreeNode = list->head;//设置一个标记用来标志释放要删除的节点
list->head = list->head->next;//头指针往后移动
list->head->prior = NULL;//头指针的前驱置空
free(FreeNode);//释放要删除的节点
list->len--;//长度-1
return ;
}
if(index == list->len-1)//判断当前节点是否为最后一个节点
{
Node *FreeNode = list->tail;//设置一个标记用来标志释放要删除的节点
list->tail = list->tail->prior;//尾部指针往前移动
list->tail->next = NULL;//尾指针的前驱置空
free(FreeNode);//释放要删除的节点
list->len--;//长度-1
return ;
}
Node *TravelPoint = list->head;//设置一个指针遍历链表,找到要删除的位序
while(index > 0)
{
TravelPoint = TravelPoint->next;
index--;
}
Node *PriorNode = TravelPoint->prior;//设置一个指针指向当前节点的前驱节点
Node *NextNode = TravelPoint->next;//设置一个节点指向当前节点的后续节点
PriorNode->next = NextNode;//前驱节点的后继指向后继节点
NextNode->prior = PriorNode;//后继节点的前驱指向前驱节点
free(TravelPoint);//释放当前节点
list->len--;//长度减一
}
【2】、按值删除
封装一个函数用于查找链表的第一个值为element的节点位置
int FindFirstByElement(DLlist *list,ElementType element)
{
int count=0;//设置一个计数器用于记录当前节点的位序
Node *travelPoint = list->head;//设置一个游历指针标记当前节点
while(travelPoint != NULL)
{
if(travelPoint->data == element)//判断当前节点的值是否为我们要找的值
{
return count;//找到返回当前位序
}
count++;//未找到继续向后移动,位序加一
travelPoint = travelPooint->next;
}
return -1;//未找到,则返回一个非法值
}
void RemoveByElement(DLlist *list,ElementType element)
{
int index = FindFirstByElement(list,element);
while(index != -1)
{
RemoveByIndex(list,index);//删除当前节点
index = FindFirstByElement(list,element);//继续找值为element的节点位序
}
}
(5)、遍历链表
void Travel(DLlist *list)
{
printf("----------------------------------------------------\n");
printf("length : %d \n",list->len);
printf("next : ");//从头往后输出
Node * travelPoint = list->head;
while(travelPoint != NULL)
{
printf("%d ",travelPoint->data);
travelPoint = travelPoint->next;
}
printf("\n");
printf("prev : ");//从后往前输出
travelPoint = list->tail;
while(travelPoint != NULL)
{
printf("%d ",travelPoint->data);
travelPoint = travelPoint->prior;
}
printf("\n");
}
(6)、释放链表
void FreeDLlist(DLlist *list)
{
while(list->head != NULL)//判断头节点是否为空
{
Node * freeNode = list->head;//设置指针用来标志要释放的节点
list->head = list->head->next;//头指针向后移
free(freeNode);//释放该节点,直至头节点为空
}
list->head = NULL;//所有指针置空,链表长度全部置为0
list->tail = NULL;
list->len = 0;
}
2、循环双链表
(1)、定义:首尾相连的双链表
- 头结点的
prior
指针指向尾结点 - 尾结点的
next
指针指向头结点
#define ElementType int
typedef struct node
{
ElementType data;
struct node * next;
struct node * prior;
}Node;
struct DoubleCircleLinkList
{
struct node *head;
struct node *tail;
int len;
};
(2)、循环双链表的初始化
int InitDCLlist(DLlist *list)
{
list->head = NULL;//设置头指针为空
list->tail = NULL;//尾指针为空
list->len = 0;//设置初始化长度为0
return 0;
}
(3)、双向循环链表的插入
封装一个函数创建新节点
Node * CreateNode(ElementType element)
{
Node *NewNode = (Node*)malloc(sizeof(Node));//动态申请一个新节点
if(NewNode == NULL)//判断申请是否成功
{
printf("CreateNode malloc error!\n");//申请失败打印错误信息,返回空指针
return NULL;
}
NewNode->prior = NULL;//初始化新节点的前驱和后继为空
NewNode->next = NULL;
NewNode->data = element;//新节点的数值为要插入的数值
return NewNode;//返回新节点
}
【1】、尾插法
void InsertTail(DLlist *list,ElementType element)
{
Node *NewNode = CreateNode(element);//申请新节点
if(NewNode == NULL)//判断新节点是否申请成功
{
printf("InsertTail malloc error!\n");
return ;
}
if(list->len == 0)//因为当链表为空时,尾指针没有next会报错,所以要判断链表是否为空
{
list->head = NewNode;//如果是第一个节点,则将所有的指针指向它
list->tail = MewNode;
}
else
{
list->tail->next = NewNode;//尾指针的后继指向新节点
NewNode->prior = list->tail;//新节点的前驱指向链表的尾部
list->tail = NewNode;//更新尾部节点
//首尾相连
list->tail->next = list->head;
list->head->prior = list->tail;
}
list->len++;//长度加一
}
【2】、头插法
void InsertHead(DCLlist *list, ElementType element)
{
InsertTail(list,element);//使用头插法
if(list->len >1)
{
list->tail = list->tail->prior;//首尾相接
list->head = list->head->prior;
}
}
(4)、删除节点
【1】、按位删除
void RemoveByIndex(DLlist *list,int index)
{
if(index < 0 || index >= list->len)//判断删除位置是否合法
{
printf("Remove invalid place!\n");
return ;
}
if(index == 0)//判断有删除的位置是否为第一位
{
if(list->len == 1)//判断当前链表是否只有一个节点
{
free(list->head);//释放头节点
list->head = NULL;//当前链表已无任何节点,头指针和尾指针全部置空
list->tail = NULL;
list->len --;//链表的长度-1
return ;
}
Node *FreeNode = list->head;//设置一个标记用来标志释放要删除的节点
list->head = list->head->next;//头指针往后移动
list->head->prior = list->tail;//头指针的前驱指向尾部节点
list->tail->next = list->head;//尾指针的后继节点连接头节点
free(FreeNode);//释放要删除的节点
list->len--;//长度-1
return ;
}
if(index == list->len-1)//判断当前节点是否为最后一个节点
{
Node *FreeNode = list->tail;//设置一个标记用来标志释放要删除的节点
list->tail = list->tail->prior;//尾部指针往前移动
list->tail->next = list->head;//尾指针的后继指向头节点
list->head->prior = list->tail;//头指针的前驱指向尾指针
free(FreeNode);//释放要删除的节点
list->len--;//长度-1
return ;
}
Node *TravelPoint = list->head;//设置一个指针遍历链表,找到要删除的位序
while(index > 0)
{
TravelPoint = TravelPoint->next;
index--;
}
Node *PriorNode = TravelPoint->prior;//设置一个指针指向当前节点的前驱节点
Node *NextNode = TravelPoint->next;//设置一个节点指向当前节点的后续节点
PriorNode->next = NextNode;//前驱节点的后继指向后继节点
NextNode->prior = PriorNode;//后继节点的前驱指向前驱节点
free(TravelPoint);//释放当前节点
list->len--;//长度减一
}
【2】、按值删除
int FindFirstByElement(DLlist *list,ElementType element)
{
int count=0;//设置一个计数器用于记录当前节点的位序
Node *travelPoint = list->head;//设置一个游历指针标记当前节点
while(travelPoint != list->tail)//循环遍历直到最后一个节点前
{
if(travelPoint->data == element)//判断当前节点的值是否为我们要找的值
{
return count;//找到返回当前位序
}
count++;//未找到继续向后移动,位序加一
travelPoint = travelPooint->next;
}
if(list->tail != NULL && list->tail->data == element)//判断最后一个节点的数据是否为我们要找的值
{
return count;
}
return -1;//未找到,则返回一个非法值
}
void RemoveByElement(DLlist *list,ElementType element)
{
int index = FindFirstByElement(list,element);
while(index != -1)
{
RemoveByIndex(list,index);//删除当前节点
index = FindFirstByElement(list,element);//继续找值为element的节点位序
}
}
(5)、遍历链表
void Travel(DLlist *list)
{
printf("----------------------------------------------------\n");
printf("length : %d \n",list->len);
printf("next : ");//从头往后输出
Node * travelPoint = list->head;
while(travelPoint != list->tail)
{
printf("%d ",travelPoint->data);
travelPoint = travelPoint->next;
}
printf("%d ",list->tail->data);
printf("\n");
printf("prev : ");//从后往前输出
travelPoint = list->tail;
while(travelPoint != list->head)
{
printf("%d ",travelPoint->data);
travelPoint = travelPoint->prior;
}
printf("%d ",liat->head->data);
printf("\n");
}
(6)、释放链表
void FreeDLlist(DLlist *list)
{
if(list->len == 0)//判断链表长度是否为空
{
return ;
}
while(list->head != list->tail)//循环遍历直至到最后一个节点
{
Node * freeNode = list->head;//设置指针用来标志要释放的节点
list->head = list->head->next;//头指针向后移
free(freeNode);//释放该节点,直至头节点为空
}
free(list->head);//释放最后一个节点
list->head = NULL;//所有指针置空,链表长度全部置为0
list->tail = NULL;
list->len = 0;
}
-
循环双向链表的应用:约瑟夫环算法
据说著名犹太历史学家Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从。首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第k个人。接着,再越过k-1个人,并杀掉第k个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。问题是,给定了和,一开始要站在什么地方才能避免被处决。Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。
void yuesefu1(DCLlist *list,int index)
{
int count = 1;
Node *TravelPoint = list->head;
while(list->len != 1)
{
if(count == 3)
{
printf(" %d is out!\n",TravelPoint->data);
Node *temp = TravelPoint->next;
RemoveByElement(list,TravelPoint->data);
TravelPoint = temp;
count = 0;
}
else
{
TravelPoint = TravelPoint->next;
}
count++;
}
printf(" %d is alive!\n",list->head->data);
}
3、链式栈
(1)、定义:用链表存储栈的结构,利用双向链表
#include"DoubleLinkList.h"
typedef struct LinkStack
{
DLlist stack;
ElementType TopElement;
}LStack;
(2)、链表初始化
int InitLStack(LStack *s)
{
return InitDLlist(&s->stack);
}
(3)、进栈
void Push(LStack *s, ElementType element)
{
InsertTail(&s->stack,element);
}
(4)、出栈
ElementType* Pop(LStack *s)
{
//static ElementType temp;
if(s->stack.len == 0)
{
printf("The Stack id empty!\n");
return NULL;
}
s->TopElement = s->stack.tail->data;
RemoveByIndex(&s->stack,s->stack.len-1);
return &s->TopElement;
}
(5)、获取栈顶指针
Node *GetTop(LStack *s)
{
return s->stack.tail;
}
(6)、判断是否栈空
int IsEmpty(LStack *s)
{
if(s->stack.len == 0)
{
return true;
}
else
{
return false;
}
}
(7)、遍历栈
void StackTravel(LStack *s)
{
Travel(&s->stack);
}
-
栈的应用一、数学表达式求值
算法思想:判断进栈的元素为数字还是运算符,使用两个栈分别存储两种数据;如果碰到运算符,判断运算符栈栈顶元素和当前运算符的优先级,当前运算符高则将当前运算符压入栈,如果栈顶运算符的优先级高则弹出栈在数字栈中取出两个栈顶元素分别对其操作并压入栈,直至运算符栈为空,此时数字栈中的元素即为最终结果。
//判断当前字符是否为数字
int IsNum(char c)
{
if(c >= '0' && c <= '9')
{
return true;
}
else
{
return false;
}
}
//判断当前字符是否为操作符
int IsOper(char c)
{
switch(c)
{
case '+':
case '-':
case '*':
case '/':
case '(':
case ')':
case '=':
return true;
default:
return false;
}
}
//当遇到'+'、'-'、'*'、'/'所要进行的操作
int Operate(int prev,int next,char symbol)
{
switch (symbol)
{
case '+': return prev+next;
case '-': return prev-next;
case '*': return prev*next;
case '/':
if(next == 0)
{
printf("devide zero!\n");
exit(-1);
}
return prev/next;
default:
break;
}
}
//设置运算符优先级,设置一个[7][7]的二维数组,用来储存两种运算符相遇可能出现的情况
char Precede(char ch1,char ch2)
{
char pre[7][7]=
{ // + - * / ( ) =
{'>','>','<','<','<','>','>'},// +
{'>','>','<','<','<','>','>'},// -
{'>','>','>','>','<','>','>'},// *
{'>','>','>','>','<','>','>'},// /
{'<','<','<','<','<','=','0'},// (
{'>','>','>','>','0','>','>'},// )
{'0','0','0','0','0','0','='} // =
};
int i=0,j=0;
switch(ch1)
{
case '+': i=0;break;
case '-': i=1;break;
case '*': i=2;break;
case '/': i=3;break;
case '(': i=4;break;
case ')': i=5;break;
case '=': i=6;break;
default:
break;
}
switch (ch2)
{
case '+': j=0;break;
case '-': j=1;break;
case '*': j=2;break;
case '/': j=3;break;
case '(': j=4;break;
case ')': j=5;break;
case '=': j=6;break;
default:
break;
}
return pre[i][j];
}
//主函数进行运算
int main()
{
LStack Num;
InitLStack(&Num);
LStack OPer;
InitLStack(&OPer);
char str[100] = {0};
printf("Please enter what you want to calculate:\n");
scanf("%[^\n]",str);
int i=0;
while(str[i] != '\0')
{
if(IsOper(str[i]) == true)
{
if(IsEmpty(&OPer) == true)
{
Push(&OPer,str[i]);
i++;
}
else
{
char symbol = Precede(GetTop(&OPer)->data,str[i]);
switch (symbol)
{
case '<': Push(&OPer,str[i]);
i++;
break;
case '>':
int next= *Pop(&Num);
int prev= *Pop(&Num);
char s= *Pop(&OPer);
Push(&Num,Operate(prev,next,s));
break;
case '=':
Pop(&OPer);
i++;
break;
default:
break;
}
}
}
else if(IsNum(str[i]))
{
int num = str[i] - '0';
Push(&Num,num);
i++;
while(str[i] == ' ')
{
i++;
}
while(IsNum(str[i]))
{
int higher = *Pop(&Num);
Push(&Num,higher*10+str[i]-'0');
i++;
while(str[i] == ' ')
{
i++;
}
}
}
else if(str[i] == ' ')
{
while(str[i] == ' ')
{
i++;
}
}
}
printf("the answer is %d\n ",GetTop(&Num)->data);
return 0;
}
- 应用二:进制转换
void Conversion(LStack *stack)
{
int num=0;
printf("Please input a number:");
scanf("%d",&num);
int n=0;
printf("Please input a system num :");
scanf("%d",&n);
while(num != 0)
{
Push(stack,num % n);
num = num /n;
}
while(IsEmpty(stack) == false)
{
printf("%d",*Pop(stack));
}
printf("\n");
}
- 应用三:判断括号匹配问题
int IsValid(char *s)
{
LStack OPer;
InitLStack(&OPer);
while(*s != '\0')
{
if(*s == '(' || *s == '[' || *s == '{')
{
Push(&OPer,*s);
}
else if(IsEmpty(&OPer))
{
return false;
}
else if(*s == ')' && GetTop(&OPer)->data == '(' ||
*s == ']' && GetTop(&OPer)->data == '[' ||
*s == '}' && GetTop(&OPer)->data == '{')
{
Pop(&OPer);
}
else
{
return false;
}
s++;
}
return IsEmpty(&OPer);
}
4、队列FIFO(用双向链表存储)
(1)、定义:队列简称队,也是一种操作受限的线性表,只允许在表的一端进行插入,在另一端进行删除。向队列中插入元素称为入队或者进队,删除元素称为出队或离队。
队头(front) : 允许删除的一端,链式存储相当于头指针 : head
队尾(rear):允许插入的一端,链式存储相当于尾指针 :tail
struct LinkQueue
{
DLlist queue;
ElementType FrontData;
};
typedef struct LinkQueue LQueue;
(2)、队列的初始化
int InitLQueue(LQueue *lq)
{
return InitDLlist(&lq->queue);
}
(3)、队列的插入
void QPush(LQueue *lq, ElementType element)
{
InsertTail(&lq->queue,element);
}
(4)、出队
ElementType *QPop(LQueue *lq)
{
if(lq->queue.len == 0)
{
printf("Queue is Empty!\n");
return NULL;
}
lq->FrontData = lq->queue.head->data;
RemoveByIndex(&lq->queue,0);
return &lq->FrontData;
}
(5)、获取队头元素
Node *GetFront(LQueue *lq)
{
return lq->queue.head;
}
(6)、释放队列
void FreeQueue(LQueue *lq)
{
FreeDLlist(&lq->queue);
}
(7)、判断队列是否为空
int IsQEmpty(LQueue *lq)
{
if(lq->queue.len == 0)
{
return true;
}
else
{
return false;
}
}