29.STL中list的实现

29.STL中list的实现

1.顺序表

1.顺序表的定义

  • 顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
  • 顺序表:可动态增长的数组,要求数据是连续存储的
typedef struct _SqList SqList;

struct _SqList
{
	int* elems;//顺序表的基地址
	int length;//顺序表的长度
	int size;//顺序表总的空间大小
};
typedef struct
{
	int* elems;//顺序表的基地址
	int length;//顺序表的长度
	int size;//顺序表总的空间大小
}SqList;

大话数据结构版

#define MAXSIZE 20          /* 存储空间初始分配量 */
typedef int ElemType;       /* ElemType类型根据实际情况而定,这里为int */
typedef struct
{
	ElemType data[MAXSIZE]; /* 数组,存储数据元素 */
	int length;             /* 线性表当前长度 */
}SqList;

2.顺序表的初始化

bool initList(SqList& L)//构造一个空的顺序表L
{
	L.elems = new int[MAX_SIZE];//为顺序表分配MAX_SIZE个int元素的空间
	if (!L.elems) return false;

	L.length = 0;
	L.size = MAX_SIZE;
	return true;
}

3.顺序表的打印

void listPrint(SqList& L)
{
	cout << "顺序表的存储空间size:" << L.size << ",已保存的元素个数length:" << L.length << endl;
	for (int i = 0; i <= L.length - 1; i++)
	{
		cout << L.elems[i] << " ";
	}
	cout << endl;
}

4.表尾添加元素

bool listAppend(SqList& L, int e)
{
	if (L.length == L.size)  return false;//存储空间已满

	L.elems[L.length] = e;
	L.length++;//表长增加1
	return true;
}
//2.添加元素
int count = 0;
cout << "请输入要添加的元素个数:" << endl;
cin >> count;

for (int i = 0; i < count; i++)
{
    cout << "\n请输入要添加的元素e:";
    cin >> e;
    if (listAppend(list, e))
    {
        cout << "添加成功!" << endl;
    }
    else
    {
        cout << "添加失败!" << endl;
    }
}
listPrint(list);

5.表中任意位置插入元素

bool listInsert(SqList &L, int i, int e)
{
	if (i < 0 || i >= L.length) return false;//i值不合法
	if (L.length == L.size) return false;//存储空间已满

	for (int j = L.length - 1; j >= i; j--)
	{
		L.elems[j + 1] = L.elems[j];//从最后一个元素开始后移,直到下标为i的元素后移
	}

	L.elems[i] = e;//将新元素e放入第i的位置
	L.length++;//表长加1

	return true;
}
//3.插入元素
cout << "请输入要插入元素的个数,(先输入插入位置,后输入元素个数):" << endl;
cin >> i >> e;

if (listInsert(list, i, e))
{
    cout << "插入成功!" << endl;
}
else
{
    cout << "插入失败!" << endl;
}
listPrint(list);

6.表中任意位置删除元素

bool listDelete(SqList& L, int i)
{
	if (i < 0 || i >= L.length) return false;//删除位置无效

	if (i == L.length - 1)//删除最后一个元素
	{
		L.length--;
		return true; 
	}

	for (int j = i; j < L.length - 1; j++)
	{
		L.elems[j] = L.elems[j + 1];//删除位置的后续元素依次往前移
	}
	L.length--;
	return true;
}
//4.删除元素
cout << "请输入要删除元素的位置:";
cin >> i;
if (listDelete(list, i))
{
    cout << "删除成功!" << endl;
}
else
{
    cout << "删除失败!" << endl;
}
listPrint(list);

7.链表销毁

void destroyList(SqList& L)
{
	if (L.elems) delete[]L.elems;//释放存储空间
	L.length = 0;
	L.size = 0;
}

8.获取元素

//获取元素
bool ListGetElement(SqList &L, int i, int &e)
{
	if (L.length == 0 || i < 0 || i > L.length - 1)  return false;

	e = L.elems[i];

	return true;
}

完整代码

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;

#define MAX_SIZE 100

//typedef struct _SqList SqList;
//
//struct _SqList
//{
//	int* elems;//顺序表的基地址
//	int length;//顺序表的长度
//	int size;//顺序表总的空间大小
//};

typedef struct
{
	int* elems;//顺序表的基地址
	int length;//顺序表的长度
	int size;//顺序表总的空间大小
}SqList;

bool initList(SqList& L)//构造一个空的顺序表L
{
	L.elems = new int[MAX_SIZE];//为顺序表分配MAX_SIZE个int元素的空间
	if (!L.elems) return false;

	L.length = 0;
	L.size = MAX_SIZE;
	return true;
}

void listPrint(SqList& L)
{
	cout << "顺序表的存储空间size:" << L.size << ",已保存的元素个数length:" << L.length << endl;
	for (int i = 0; i <= L.length - 1; i++)
	{
		cout << L.elems[i] << " ";
	}
	cout << endl;
}

bool listAppend(SqList& L, int e)
{
	if (L.length == L.size)  return false;//存储空间已满

	L.elems[L.length] = e;
	L.length++;//表长增加1
	return true;
}

bool listInsert(SqList &L, int i, int e)
{
	if (i < 0 || i >= L.length) return false;//i值不合法
	if (L.length == L.size) return false;//存储空间已满

	for (int j = L.length - 1; j >= i; j--)
	{
		L.elems[j + 1] = L.elems[j];//从最后一个元素开始后移,直到下标为i的元素后移
	}

	L.elems[i] = e;//将新元素e放入第i的位置
	L.length++;//表长加1

	return true;
}

bool listDelete(SqList& L, int i)
{
	if (i < 0 || i >= L.length) return false;//删除位置无效

	if (i == L.length - 1)//删除最后一个元素
	{
		L.length--;
		return true; 
	}

	for (int j = i; j < L.length - 1; j++)
	{
		L.elems[j] = L.elems[j + 1];//删除位置的后续元素依次往前移
	}
	L.length--;
	return true;
}

void destroyList(SqList& L)
{
	if (L.elems) delete[]L.elems;//释放存储空间
	L.length = 0;
	L.size = 0;
}

int main()
{
	SqList list;
	int i, e;
	cout << "顺序表的初始化:" << endl;
	//1.初始化
	if (initList(list))
	{
		cout << "顺序表初始化成功!" << endl;
	}

	listPrint(list);

	//2.添加元素
	int count = 0;
	cout << "请输入要添加的元素个数:" << endl;
	cin >> count;

	for (int i = 0; i < count; i++)
	{
		cout << "\n请输入要添加的元素:";
		cin >> e;
		if (listAppend(list, e))
		{
			cout << "添加成功!" << endl;
		}
		else
		{
			cout << "添加失败!" << endl;
		}
	}
	listPrint(list);

	//3.插入元素
	cout << "请输入要插入元素的个数,(先输入插入位置,后输入所插入的元素):" << endl;
	cin >> i >> e;

	if (listInsert(list, i, e))
	{
		cout << "插入成功!" << endl;
	}
	else
	{
		cout << "插入失败!" << endl;
	}
	listPrint(list);

	//4.删除元素
	cout << "请输入要删除元素的位置:";
	cin >> i;
	if (listDelete(list, i))
	{
		cout << "删除成功!" << endl;
	}
	else
	{
		cout << "删除失败!" << endl;
	}
	listPrint(list);

	//5.销毁链表
	destroyList(list);

	system("pause");
	return EXIT_SUCCESS;
}

大话数据结构课程代码

Status ListInsert(StaticLinkList L, int i, ElemType e)   
{  
	int j, k, l;   
	k = MAXSIZE - 1;   					/* 注意k首先是最后一个元素的下标 */
	if (i < 1 || i > ListLength(L) + 1)   
		return ERROR;   
	j = Malloc_SSL(L);   				/* 获得空闲分量的下标 */
	if (j)   
	{   
		L[j].data = e;   				/* 将数据赋值给此分量的data */
		for(l = 1; l <= i - 1; l++)   	/* 找到第i个元素之前的位置 */
		   k = L[k].cur;           
		L[j].cur = L[k].cur;    		/* 把第i个元素之前的cur赋值给新元素的cur */
		L[k].cur = j;           		/* 把新元素的下标赋值给第i个元素之前元素的ur */
		return OK;   
	}   
	return ERROR;   
}

/*将所有的在线性表Lb中但不在La中的数据元素插入到La中*/
void unionL(SqList *La,SqList Lb)
{
	int La_len,Lb_len,i;
	ElemType e;                        /*声明与La和Lb相同的数据元素e*/
	La_len=ListLength(*La);            /*求线性表的长度 */
	Lb_len=ListLength(Lb);
	for (i=1;i<=Lb_len;i++)
	{
		GetElem(Lb,i,&e);              /*取Lb中第i个数据元素赋给e*/
		if (!LocateElem(*La,e))        /*La中不存在和e相同数据元素*/
			ListInsert(La,++La_len,e); /*插入*/
	}
}

#define MAXSIZE 20          /* 存储空间初始分配量 */
typedef int ElemType;       /* ElemType类型根据实际情况而定,这里为int */
typedef struct
{
	ElemType data[MAXSIZE]; /* 数组,存储数据元素 */
	int length;             /* 线性表当前长度 */
}SqList;

#define OK 1
#define ERROR 0
/* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int Status;         

/* 初始条件:顺序线性表L已存在,1≤i≤ListLength(L) */
/* 操作结果:用e返回L中第i个数据元素的值,注意i是指位置,第1个位置的数组是从0开始 */
Status GetElem(SqList L,int i,ElemType *e)
{
	if(L.length==0 || i<1 || i>L.length)
		return ERROR;
	*e=L.data[i-1];

	return OK;
}

/* 初始条件:顺序线性表L已存在,1≤i≤ListLength(L), */
/* 操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1 */
Status ListInsert(SqList *L,int i,ElemType e)
{ 
	int k;
	if (L->length==MAXSIZE)  			/* 顺序线性表已经满 */
		return ERROR;
	if (i<1 || i>L->length+1)			/* 当i比第一位置小或者比最后一位置后一位置还要大时 */
		return ERROR;				

	if (i<=L->length)        			/* 若插入数据位置不在表尾 */
	{
		for(k=L->length-1;k>=i-1;k--)  	/* 将要插入位置后的元素向后移一位 */
			L->data[k+1]=L->data[k];
	}
	L->data[i-1]=e;          			/* 将新元素插入 */
	L->length++;

	return OK;
}


/* 初始条件:顺序线性表L已存在,1≤i≤ListLength(L) */
/* 操作结果:删除L的第i个数据元素,并用e返回其值,L的长度减1 */
Status ListDelete(SqList *L,int i,ElemType *e) 
{ 
	int k;
	if (L->length==0)               /* 线性表为空 */
		return ERROR;
	if (i<1 || i>L->length)         /* 删除位置不正确 */
		return ERROR;
	*e=L->data[i-1];
	if (i<L->length)                /* 如果删除不是最后位置 */
	{
		for(k=i;k<L->length;k++)	/* 将删除位置后继元素前移 */
			L->data[k-1]=L->data[k];
	}
	L->length--;
	return OK;
}

/* 线性表的单链表存储结构 */
typedef struct Node
{
	ElemType data;
	struct Node *next;
}Node;
typedef struct Node *LinkList; /* 定义LinkList */

/* 初始条件:链式线性表L已存在,1≤i≤ListLength(L) */
/* 操作结果:用e返回L中第i个数据元素的值 */
Status GetElem(LinkList L,int i,ElemType *e)
{
	int j;
	LinkList p;		/* 声明一结点p */
	p = L->next;		/* 让p指向链表L的第一个结点 */
	j = 1;		/*  j为计数器 */
	while (p && j<i)  /* p不为空或者计数器j还没有等于i时,循环继续 */
	{   
		p = p->next;  /* 让p指向下一个结点 */
		++j;
	}
	if ( !p || j>i ) 
		return ERROR;  /*  第i个元素不存在 */
	*e = p->data;   /*  取第i个元素的数据 */
	return OK;
}

s->next = p->next;    /* 将p的后继结点赋值给s的后继  */
p->next = s;          /* 将s赋值给p的后继 */


/* 初始条件:链式线性表L已存在,1≤i≤ListLength(L), */
/* 操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1 */
Status ListInsert(LinkList *L,int i,ElemType e)
{ 
	int j;
	LinkList p,s;
	p = *L;   
	j = 1;
	while (p && j < i)     				/* 寻找第i个结点 */
	{
		p = p->next;
		++j;
	} 
	if (!p || j > i) 
		return ERROR;   				/* 第i个元素不存在 */

	s = (LinkList)malloc(sizeof(Node)); /* 生成新结点(C语言标准函数) */
	s->data = e;  
	s->next = p->next;    				/* 将p的后继结点赋值给s的后继 */
	p->next = s;          				/* 将s赋值给p的后继 */
	return OK;
}

q = p->next;
p->next = q->next;			/* 将q的后继赋值给p的后继 */

/* 初始条件:链式线性表L已存在,1≤i≤ListLength(L) */
/* 操作结果:删除L的第i个数据元素,并用e返回其值,L的长度减1 */
Status ListDelete(LinkList *L,int i,ElemType *e) 
{ 
	int j;
	LinkList p,q;
	p = *L;
	j = 1;
	while (p->next && j < i)	/* 遍历寻找第i个元素 */
	{
		p = p->next;
		++j;
	}
	if (!(p->next) || j > i) 
		return ERROR;           /* 第i个元素不存在 */
	q = p->next;
	p->next = q->next;			/* 将q的后继赋值给p的后继 */
	*e = q->data;               /* 将q结点中的数据给e */
	free(q);                    /* 让系统回收此结点,释放内存 */
	return OK;
}

/*  随机产生n个元素的值,建立带表头结点的单链线性表L(头插法) */
void CreateListHead(LinkList *L, int n) 
{
	LinkList p;
	int i;
	srand(time(0));                         /* 初始化随机数种子 */
	*L = (LinkList)malloc(sizeof(Node));
	(*L)->next = NULL;                      /* 先建立一个带头结点的单链表 */
	for (i=0; i<n; i++) 
	{
		p = (LinkList)malloc(sizeof(Node)); /* 生成新结点 */
		p->data = rand()%100+1;             /* 随机生成100以内的数字 */
		p->next = (*L)->next;    
		(*L)->next = p;						/* 插入到表头 */
	}
}

/*  随机产生n个元素的值,建立带表头结点的单链线性表L(尾插法) */
void CreateListTail(LinkList *L, int n) 
{
	LinkList p,r;
	int i;
	srand(time(0));                     	/* 初始化随机数种子 */
	*L = (LinkList)malloc(sizeof(Node)); 	/* L为整个线性表 */
	r=*L;                                	/* r为指向尾部的结点 */
	for (i=0; i<n; i++) 
	{
		p = (Node *)malloc(sizeof(Node)); 	/* 生成新结点 */
		p->data = rand()%100+1;           	/* 随机生成100以内的数字 */
		r->next=p;                        	/* 将表尾终端结点的指针指向新结点 */
		r = p;                            	/* 将当前的新结点定义为表尾终端结点 */
	}
	r->next = NULL;                       	/* 表示当前链表结束 */
}

/* 初始条件:链式线性表L已存在。操作结果:将L重置为空表 */
Status ClearList(LinkList *L)
{ 
	LinkList p,q;
	p=(*L)->next;           /*  p指向第一个结点 */
	while(p)                /*  没到表尾 */
	{
		q=p->next;
		free(p);
		p=q;
	}
	(*L)->next=NULL;        /* 头结点指针域为空 */
	return OK;
}

#define MAXSIZE 1000 	/* 存储空间初始分配量 */

/* 线性表的静态链表存储结构 */
typedef struct 
{
	ElemType data;
	int cur;  			/* 游标(Cursor) ,为0时表示无指向 */
} Component,StaticLinkList[MAXSIZE];



/* 将一维数组space中各分量链成一个备用链表,space[0].cur为头指针,"0"表示空指针 */
Status InitList(StaticLinkList space) 
{
	int i;
	for (i=0; i<MAXSIZE-1; i++)  
		space[i].cur = i+1;
	space[MAXSIZE-1].cur = 0; 	/* 目前静态链表为空,最后一个元素的cur为0 */
	return OK;
}

/* 若备用空间链表非空,则返回分配的结点下标,否则返回0 */
int Malloc_SSL(StaticLinkList space) 
{ 
	int i = space[0].cur;           		/* 当前数组第一个元素的cur存的值 */
											/* 就是要返回的第一个备用空闲的下标 */
	if (space[0]. cur)         
		space[0]. cur = space[i].cur;       /* 由于要拿出一个分量来使用了, */
											/* 所以我们就得把它的下一个 */
											/* 分量用来做备用 */
	return i;
}

/*  删除在L中第i个数据元素   */
Status ListDelete(StaticLinkList L, int i)   
{ 
	int j, k;   
	if (i < 1 || i > ListLength(L))   
		return ERROR;   
	k = MAXSIZE - 1;   
	for (j = 1; j <= i - 1; j++)   
		k = L[k].cur;   
	j = L[k].cur;   
	L[k].cur = L[j].cur;   
	Free_SSL(L, j);   
	return OK;   
} 

/*  将下标为k的空闲结点回收到备用链表 */
void Free_SSL(StaticLinkList space, int k) 
{  
	space[k].cur = space[0].cur;    /* 把第一个元素的cur值赋给要删除的分量cur */
	space[0].cur = k;               /* 把要删除的分量下标赋值给第一个元素的cur */
}

/* 初始条件:静态链表L已存在。操作结果:返回L中数据元素个数 */
int ListLength(StaticLinkList L)
{
	int j=0;
	int i=L[MAXSIZE-1].cur;
	while(i)
	{
		i=L[i].cur;
		j++;
	}
	return j;
}

p=rearA->next;   			    /* 保存A表的头结点,即① */
rearA->next=rearB->next->next;	/* 将本是指向B表的第一个结点(不是头结点)*/
                 				/* 赋值给reaA->next,即② */
q=rearB->next;
rearB->next=p;				   	/* 将原A表的头结点赋值给rearB->next,即③ */
free(q);					   	/* 释放q */

/*线性表的双向链表存储结构*/
typedef struct DulNode
{
		ElemType data;
		struct DuLNode *prior;    	/*直接前驱指针*/
		struct DuLNode *next;		/*直接后继指针*/
} DulNode, *DuLinkList;

p->next->prior = p = p->prior->next

s - >prior = p;   			/*把p赋值给s的前驱,如图中①*/
s -> next = p -> next;		/*把p->next赋值给s的后继,如图中②*/
p -> next -> prior = s;		/*把s赋值给p->next的前驱,如图中③*/
p -> next = s;				/*把s赋值给p的后继,如图中④*/

p->prior->next=p->next;   	/*把p->next赋值给p->prior的后继,如图中①*/
p->next->prior=p->prior;	/*把p->prior赋值给p->next的前驱,如图中②*/
free(p);					/*释放结点*/

参考资料来源:

奇牛学院

2.单链表

1.链表结构

typedef struct _LinkNode
{
	int data; //结点的数据域
	struct _LinkNode* next; //结点的指针域
}LinkNode, LinkList; //LinkList 为指向结构体LNode 的指针类型

2.链表初始化

bool InitList(LinkList*& L)
{
	L = new LinkNode;

	if (!L) return false;//生成节点失败

	L->next = NULL;
	return true;
}

3.插入数据

3.1前插

//前插法
bool ListInsert_front(LinkList* &L, LinkNode *node)
{
	if (!L || !node) return false;

	node->next = L->next;
	L->next = node;
}

3.2尾插

//尾插法
bool ListInsert_back(LinkList*& L, LinkNode* node)
{
	LinkNode* last = NULL;

	if (!L || !node) return false;

	last = L;

	while (last->next) last = last->next;

	node->next = NULL;
	last->next = node;
	return true;
}

3.3任意位置插入元素

//指定位置插入元素
bool LinkInsert(LinkList*& L, int i, int& e)
{
	if (!L) return false;

	int j = 0;//当前位置
	LinkList* p, *s;

	p = L;
	while (p && j < i - 1)//查找位置为i - 1的结点,p指向该结点
	{
		p = p->next;
		j++;
	}

	if (!p || j > i - 1)//无效位置
	{
		return false;
	}

	s = new LinkNode;//生成新节点
	s->data = e;
	s->next = p->next;
	p->next = s;
}

4.打印

//打印
void LinkPrint(LinkList*& L)
{
	LinkNode* p = NULL;

	if (!L)
	{
		cout << "链表为空。";
		return;
	}
	p = L->next;

	while (p)
	{
		cout << p->data << "\t";
		p = p->next;
	}
	cout << endl;
}

5.查询值

5.1按位置查找值

bool Link_GetElem(LinkList* &L, int i, int &e)//单链表的取值
{
	//在带头结点的单链表L中查找第i个元素
	//用e记录L中地i个元素的值
	int index;
	LinkList* p;

	if (!L || !L->next) return false;

	p = L->next;
	index = 1;

	while (p && index < i)//顺向向后扫描,直到p指向第i个元素或p为空
	{
		p = p->next;//p指向下一个结点
		index++;//计数器index相应加1
	}

	if (!p || index > i)
		return false;//i值不合法,i>n或i<=0

	e = p->data;
	return true;
}

5.2按值查找

bool Link_FindElem(LinkList* L, int e, int &index)//按值查找
{
	//在带头结点的单链表L中查找值为e的元素

	LinkList* p;
	p = L->next;
	index = 1;
	if (!L || !L->next)
	{
		index = 0;
		return false;
	}

	while (p && p->data != e)
	{
		p = p->next;
		index++;
	}

	if (!p) 
	{
		index = 0;
		return false;//查无此值
	}

	return true;
}

6.单链表的删除

//单链表的删除
bool LinkDelete(LinkList * &L, int i)
{
	LinkList* p, *q;

	int  index = 0;
	p = L;

	if (!L || !L->next) return false;
	while ((p->next) && (index < i - 1))
	{
		p = p->next;
		index++;
	}

	if (!p->next || (index > i - 1))//当i>n或i<1时,删除位置不合法
	{
		return false;
	}
	q = p->next;//临时保存被删结点的地址以备释放空间
	p->next = q->next;//改变删除结点前驱结点的指针域
	delete q;//释放被删除结点的空间
	return true;
}

7.链表销毁

void LinkDestroy(LinkList*& L)//单链表的销毁
{
	//定义临时结点p指向头结点
	LinkList* p = L;
	cout << "销毁链表!" << endl;

	while (p)
	{
		L = L->next;//L指向下一个结点
		delete p;//删除当前结点
		p = L;//p移向下一个结点
	}
}

完整代码

#include<iostream>
#include<string>
#include<stdlib.h>
using namespace std;

typedef struct _LinkNode
{
    int data; //结点的数据域
    struct _LinkNode* next; //结点的指针域
}LinkNode, LinkList; //LinkList 为指向结构体LNode 的指针类型

bool InitList(LinkList*& L)
{
    L = new LinkNode;
    if (!L) return false;//生成节点失败
    L->next = NULL;
    L->data = -1;
    return true;
}

//前插法
bool ListInsert_front(LinkList*& L, LinkNode* node)
{
    if (!L || !node) return false;
    node->next = L->next;
    L->next = node;
    return true;
}

//尾插法
bool ListInsert_back(LinkList*& L, LinkNode* node)
{
    LinkNode* last = NULL;
    if (!L || !node) return false;
    last = L;
    while (last->next) last = last->next;
    node->next = NULL;
    last->next = node;
    return true;
}

//指定位置插入
bool LinkInsert(LinkList*& L, int i, int& e)
{
    if (!L) return false;
    int j = 0;
    LinkList* p, * s;
    p = L;
    while (p && j < i - 1)
    {
        //查找位置为i-1 的结点,p 指向该结点
        p = p->next;
        j++;
    }
    if (!p || j > i - 1)
    {
        return false;
    }
    s = new LinkNode;//生成新节点
    s->data = e; 
    s->next = p->next;
    p->next = s;
    return true;
}

void LinkPrint(LinkList*& L)
{
    LinkNode* p = NULL;
    if (!L)
    {
        cout << "链表为空." << endl;
        return;
    }
    p = L->next;
    while (p)
    {
        cout << p->data << "\t";
        p = p->next;
    }
    cout << endl;
}

bool Link_GetElem(LinkList*& L, int i, int& e)//单链表的取值
{
    //在带头结点的单链表L 中查找第i 个元素
    //用e 记录L 中第i 个数据元素的值
    int index;
    LinkList* p;
    if (!L || !L->next) return false;
    p = L->next;
    index = 1;

    while (p && index < i)
    {
        //顺链表向后扫描,直到p 指向第i 个元素或p 为空
        p = p->next; //p 指向下一个结点
        index++; //计数器index 相应加1
    }
    e = p->data;
    return true;
}

bool Link_FindElem(LinkList* L, int e, int& index) //按值查找
{
    //在带头结点的单链表L 中查找值为e 的元素
    LinkList* p;
    p = L->next;
    index = 1;
    if (!L || !L->next)
    {
        index = 0;
        return false;
    }
    while (p && p->data != e)
    {
        p = p->next;
        index++;
    }
    if (!p)
    {
        index = 0;
        return false;//查无此值
    }
    return true;
}

bool LinkDelete(LinkList*& L, int i) //单链表的删除
{
    LinkList* p, * q;
    int index = 0;
    p = L;
    if (!L || !L->next)
    {
        return false;
    }
    while ((p->next) && (index < i - 1))
    {
        p = p->next;
        index++;
    }
    if (!p || index > i)
    {
        return false; //i 值不合法,i>n 或i<=0
    }
    if (!p->next || (index > i - 1))
    {
        //当 i>n 或 i<1 时,删89除7位94置38不40合1理111
        return false;
    }
    q = p->next; //临时保存被删结点的地址以备释放空间
    p->next = q->next;//改变删除结点前驱结点的指针域
    delete q; //释放被删除结点的空间
    return true;
}
void LinkDestroy(LinkList*& L) //单链表的销毁
{
    //定义临时节点p 指向头节点
    LinkList* p = L;
    cout << "销毁链表!" << endl;
    while (p)
    {
        L = L->next;//L 指向下一个节点
        cout << "删除元素: " << p->data << endl;
        delete p; //删除当前节点
        p = L; //p 移向下一个节点
    }
}
int main(void)
{
    LinkList* L = NULL;
    LinkNode* s = NULL;
    //1. 初始化一个空的链表
    InitList(L);
    //2. 使用前插法插入数据
    /*int n;
    cout<<"前插法创建单链表"<<endl;
    std::cout<<"请输入元素个数n:";
    cin>>n;
    cout<<"\n 请依次输入n 个元素:" <<endl;
    while(n>0)
    {
        s = new LinkNode; //生成新节点s
        cin>>s->data;
        ListInsert_front(L, s);
        n--;
    }
    */
    //3. 使用尾插法插入数据
    /*
    
    
    int n;
    cout<<"尾插法创建单链表"<<endl;
    std::cout<<"请输入元素个数n:";
    cin>>n;
    cout<<"\n 请依次输入n 个元素:" <<endl;
    while(n>0)
    {
        s = new LinkNode; //生成新节点s    
        cin>>s->data;     
        ListInsert_back(L, s);
        n--;
    }
    */
    //4. 单链表的输出
    LinkPrint(L);
     
     //5.任意位置插入元素
    for (int j = 0; j < 3; j++) 
    {
        int i, x;
        cout << "请输入插入的位置和元素(用空格隔开):";
        cin >> i;
        cin >> x;
        if (LinkInsert(L, i, x)) {
            cout << "插入成功.\n\n";
        }
        else {
            cout << "插入失败!\n\n";
        }
        LinkPrint(L);
    }

    //6. 单链表根据位置获取元素
    int element = 0;
    if (Link_GetElem(L, 2, element)) 
    {
        cout << "获取第二个元素成功, 值:" << element << endl;
    }
    else 
    {
        cout << "获取第二个元素失败!" << endl;
    }
    //7. 单链表根据值查询元素所在的位置
    int index = 0;
    if (Link_FindElem(L, 10, index))
    {
        cout << "查找元素10 存在,所在位置: " << index << endl;
    }
    else 
    {
        cout << "不存在元素10." << endl;
    }
    //8. 单链表删除元素
    if (LinkDelete(L, 2))
    {
        cout << "删除第2 个元素成功!" << endl;
        LinkPrint(L);
    }
    else 
    {
        cout << "删除第2 个元素失败!" << endl;
    }
    //9. 销毁单链表
    LinkDestroy(L);
    system("pause");
    return 0;
}

参考资料:

奇牛学院

3.循环链表

1.循环链表定义

typedef struct _LinkNode
{
	int data;//结点的指针域
	struct _LinkNode* next;//结点的指针域
}LinkNode, LinkList;//LinkList为指向结构体LNode的指针类型

2.循环链表初始化

bool InitList(LinkList*& L)//构造一个空的循环链表L
{
	L = new LinkNode;//生成新结点作为头结点,用头指针L指向头结点

	if (!L) return false;//生成结点失败

	L->next = L;//头结点的指针域指向自己
	L->data = -1;
	return true;
}

3.尾插法

//尾插法
bool ListInsert_back(LinkList*& L, LinkNode* node)
{
	LinkNode* last = NULL;
	if (!L || !node) return false;

	if (L == L->next)//头结点的指针域指向了自己(空循环链表)
	{
		node->next = L;
		L->next = node;
	}
	else//非空的循环链表
	{
		last = L->next;
		while (last->next != L) last = last->next;//last定位到尾个节点

		//新的结点链接到尾部
		node->next = L;
		last->next = node;
	}
	return true;
}

4.打印

void LinkPrint(LinkList* L)
{
	LinkNode* p = NULL;
	if (!L || L==L->next)
	{
		cout << "链表为空." << endl;
		return;
	}

	p = L->next;

	while (p != L)
	{
		cout << p->data << "\t";
		p = p->next;
	}
	cout << endl;
}

5.循环链表的算法实现

Joseph 问题

有10 个小朋友按编号顺序1,2,。。。,10 顺时针方向围成一圈。从1 号开始顺时针方向1,2,。。。,9 报数,凡报数9 者出列(显然,第一个出圈为
编号9 者)。
最后一个出圈者的编号是多少?第5 个出圈者的编号是多少?


代码:

#include<iostream>
#include<string>
#include<stdlib.h>
using namespace std;

typedef struct _LinkNode 
{
	int data; //结点的数据域
	struct _LinkNode* next; //结点的指针域
}LinkNode, LinkList; //LinkList 为指向结构体LNode 的指针类型

void LinkPrint(LinkList* L);

bool InitList(LinkList*& L)//构造一个空的循环链表L
{
	L = new LinkNode; //生成新结点作为头结点,用头指针L 指向头结点
	if (!L)return false; //生成结点失败
	L->next = L; //头结点的指针域指向自己
	L->data = -1;
	return true;
}

//尾插法
bool ListInsert_back(LinkList*& L, LinkNode* node)
{
	LinkNode* last = NULL;
	if (!L || !node) return false;
	//找到最后一个节点
	last = L;
	while (last->next != L) last = last->next;
	//新的节点链接到最尾部
	node->next = L;
	last->next = node;
	return true;
}

bool Joseph(LinkList*& L, int interval)
{
	//在带头结点的循环链表L 中,每个interval 个间隔循环删除节点
	LinkList* p, * q;
	int j = 0, i = 0;
	int times = 0, num = 0;//times是删除的次数
	p = L;

	if (!L || p->next == L) {
		cout << "链表为空!" << endl;
		return false;
	}
	if (interval < 1) {
		cout << "报数淘汰口令不能小于1!" << endl;
		return false;
	}
	do
	{
		//i是要删除的位置编号
		i += interval;
		while ((p->next)) //查找第i 个结点,p 指向该结点的上一个节点
		{
			if (p->next != L) j++;//跳过头结点计数
			if (j >= i) break;//找到删除位置编号退出
			p = p->next;
		}

		times++;

		/*if (!(p->next)||(j>i))//当i>n 或i<1 时,删除位置不合理
		return false;*/

		q = p->next; //临时保存被删结点的地址以备释放空间
		num = q->data;
		if (times == 5) cout << "第 5 个出圈的编号是:" << num << endl; 
			printf("cur: %d last: %d next:%d\n", q->data, p->data, q->next->data);
		p->next = q->next; //改变删除结点前驱结点的指针域
		delete q; //释放被删除结点的空间
		LinkPrint(L);
	} while (L->next != L);//链表不为空,继续报数
	cout << "最后一个出圈的编号是:" << num << endl;
	return true;
}

void LinkPrint(LinkList* L) //循环链表的输出
{
	LinkList* p;
	if (!L || L == L->next) {
		cout << "链表为空!" << endl;
		return;
	}

	p = L->next;

	while (p != L)
	{
		cout << p->data << "\t";
		p = p->next;
	}
	cout << endl;
}

int main() 
{
	int i, x;
	LinkList* L;
	LinkNode* s;

	//1. 初始化一个空的循环链表
	if (InitList(L)) 
	{
		cout << "初始化一个空的循环链表!\n";
	}

	//2. 创建循环链表(尾插法)
	std::cout << "尾插法创建循环链表, 插入 10 个元素...8" << endl; 
	i = 0;
	while ((++i) <= 10)
	{
		s = new LinkNode;//生成新结点
		s->data = i; //输入元素值赋给新结点的数据域
		s->next = NULL;
		if (ListInsert_back(L, s)) 
		{
			cout << "插入成功!" << endl;
		}
		else
		{
			cout << "插入失败!" << endl;
		}
	}
	cout << "尾插法创建循环链表输出结果:\n";
	LinkPrint(L);
	//3. 解答约瑟夫问题
	Joseph(L, 9);
	system("pause");
	return 0;
}

参考资料来源:

奇牛学院

1.接口总览

namespace qgw 
{
	/// @brief list 中每个节点
	/// @tparam T 节点存储的数据的类型
	template <class T>
	struct _list_node
	{
		_list_node(const T& data = val());	// 节点类的构造函数

		_list_node<T>* _prev;				// 指向前一节点
		_list_node<T>* _next;				// 指向后一节点
		T _data;							// 存储节点数据
	};

	/// @brief list 的迭代器
	/// @tparam T list 数据的类型
	/// @tparam Ref 数据的引用类型
	/// @tparam Ptr 数据的指针类型
	template <class T, class Ref, class Ptr>
	struct _list_iterator 
	{
		typedef _list_iterator<T, T&, T*>		iterator;
		typedef _list_iterator<T, Ref, Ptr>		self;

		typedef Ptr		pointer;
		typedef Ref		reference;
		typedef _list_node<T> list_node;

		// 构造函数
		_list_iterator(list_node* node = nullptr);

		// 各种运算符重载
		bool operator==(const self& x) const;
		bool operator!=(const self& x) const;
		reference operator*() const;
		pointer operator->() const;
		self& operator++();
		self operator++(int);
		self& operator--();
		self operator++(int);

		list_node* _node;	// 指向对应的 list 节点
	};

	template <class T>
	class list 
	{
	public:
		typedef T* pointer;
		typedef const T* const_pointer;
		typedef T& reference;
		typedef const T& const_reference;
		typedef _list_node<T>	list_node;
		typedef _list_iterator<T, T&, T*>             iterator;
		typedef _list_iterator<T, const T&, const T*> const_iterator;

	public:
		// 默认成员函数
		list();
		list(const list<T>& other);
		list<T>& operator=(const list<T>& other);
		~list();

		// 元素访问
		reference front();
		const_reference front() const;
		reference back();
		const_reference back() const;

		// 迭代器
		iterator begin();
		const_iterator begin() const;
		iterator end();
		const_iterator end() const;

		// 容量
		bool empty() const;
		size_t size() const;

		// 修改器
		void clear();
		iterator insert(iterator pos, const_reference val);
		void push_front(const_reference val);
		void push_back(const_reference val);
		iterator erase(iterator pos);
		void pop_front();
		void pop_back();
		void swap(list& other);

		// 操作
		void splice(iterator pos, list& other);
		void splice(iterator pos, list& other, iterator it);
		void splice(iterator pos, list& other, iterator first, iterator last);
		void merge(list& other);
		void remove(const_reference value);
		void reverse();
		void unique();

	private:
		list_node* _node;	// 指向链表头节点
	};
}

2.list 的节点

list 的节点我们设计成一个 _list_node 类,里面有三个成员变量,分别为前后指针和数据。

它的构造函数将数据初始化为给定数据,再将前后指针初始化为空。

/// @brief 节点类的构造函数
/// @param data 用来构造节点的初值
_list_node(const T& data = T()) : _data(data) 
{
    _prev = nullptr;
    _next = nullptr;
}

3.默认成员函数

3.1默认构造函数

SGI list 不仅是一个双向链表,还是一个带头的循环链表。

/// @brief 构造一个空链表
list()
{
    _node = new list_node;	// 创建一个头节点
    _node->_prev = _node;	// 前面指向自己
    _node->_next = _node;	// 后面指向自己
}

3.2析构函数

list 的析构函数,先调用 clear 释放数据资源,再 delete 掉头节点即可。

/// @brief 释放资源
~list() 
{
    clear();
    delete _node;
    _node = nullptr;
}

3.3拷贝构造函数

用另一容器创建新对象。

先申请一个头节点,然后遍历 other 容器,将 other 中的数据逐一尾插到 *this 中。

/// @brief 用给定容器初始化
/// @param other 用来初始化的容器
list(const list<T>& other)
{
    _node = new list_node;
    _node->_next = _node;
    _node->_prev = _node;
    for (const auto& e : other) 
    {
        push_back(e);
    }
}

3.4复制赋值函数

先创建给定容器的拷贝 tmp,然后交换 *this 和 tmp,最后返回 *this。

/// @brief 替换容器内容
/// @param other 用作数据源的另一容器
/// @return *this
list<T>& operator=(const list<T>& other)
{
    // tmp 出了作用域就销毁了
    list<T> tmp(other);
    swap(tmp);
    // 返回引用可以连续赋值
    return *this;
}

3.5构造函数

list 的迭代器中成员变量只有一个节点指针,将其指向给定节点即可。

/// @brief list 迭代器的构造函数
/// @param node 用来构造的节点
_list_iterator(list_node* node = nullptr) 
{
    _node = node;
}

3.6运算符重载

(1)operator==

判断两迭代器指向的节点是否为同一个,直接比较迭代器中节点的指针即可。切记不能比较指针中的值,因为不同节点的值可能相同。

/// @brief 判断两迭代器指向的节点是否相同
/// @param x 用来比较的迭代器
/// @return 相同返回 true,不同返回 false
bool operator==(const self& x) const
{
    return _node == x._node;
}

(2)operator!=

!= 的比较方法和 == 一样。

/// @brief 判断两迭代器指向的节点是否不同
/// @param x 用来比较的迭代器
/// @return 不同返回 true,相同返回 false
bool operator!=(const self& x) const
{
    return _node != x._node;
}

(3)operator*

迭代器是模仿指针的,让我们可以像使用指针一样。因此可以对迭代器进行解引用操作,该操作得到的是迭代器中节点指针指向的数据,并且返回引用,因为有可能修改该数据。

/// @brief 获取指向节点中的数据值
/// @return 返回指向节点数据的引用
reference operator*() const
{
    return _node->_data;
}

(4)operator->

-> 运算符的重载稍显复杂,让我们先看下面这个场景。

也就是 list 中存储的是自定义类型,自定义类型中又有多个成员变量,我们想取出指定的成员变量,当然这里用 . 也可以做到。

// 有一个学生类,里面有姓名和学号两个成员
struct Stu
{
	string name;
	string id;
};

list<Stu> s;
Stu s1 = { "qgw", "001" };
Stu s2 = { "wlr", "002" };
s.push_back(s1);
s.push_back(s2);
list<Stu>::iterator ptr = s.begin();
// 输出第一个学生的姓名和学号
cout << (*ptr).name << endl;
cout << s.begin()->id << endl;

看到这你可能会疑惑,operator-> 返回的是节点的数据的地址,也是说上面 s.begin()-> 得到的是一个地址,那这条语句是怎么执行的?

实际上这里确实应该有两个箭头像这样 s.begin()->->id,但这种方式的可读性太差了,所以编译器对此做了优化,在编译为我们添加一个箭头。

(5)operator++

operator++ 运算符的作用十分清晰,就是让迭代器指向链表中下一节点。

前置实现的思路是:通过迭代器中的节点指针找到下一节点,然后赋值给迭代器中的节点指针。

后置实现的思路是:先保存当前位置迭代器,然后调用前置 ++,最后返回临时变量。

需要注意的是:前置 ++ 返回的是前进后迭代器的引用,后置 ++ 返回的是一个临时变量。

/// @brief 前置 ++
/// @return 返回前进一步后的迭代器
self& operator++() 
{
    _node = _node->_next;
    return *this;
}

/// @brief 后置 ++
/// @param  无作用,只是为了与前置 ++ 进行区分,形成重载
/// @return 返回当前的迭代器
self operator++(int)
{
    self tmp = *this;
    // 直接调用前置 ++
    ++(*this);
    return tmp;
}

(6)operator--

前置实现的思路是:通过迭代器中的节点指针找到前一节点,然后赋值给迭代器中的节点指针。

后置实现的思路是:先保存当前位置迭代器,然后调用前置 --,最后返回临时变量。

/// @brief 前置 --
/// @return 返回后退一步后的迭代器
self& operator--()
{
    _node = _node->_prev;
    return *this;
}

/// @brief 后置 --
/// @param  无作用,只是为了与前置 -- 进行区分,形成重载
/// @return 返回当前的迭代器
self operator--(int) 
{
    self tmp = *this;
    --(*this);
    return tmp;
}

4.list 的迭代器

list 的节点在内存中不是连续存储的,因此不能使用原生指针作为 list 的迭代器。list 的迭代器必须有能力指向 list 的节点,并能够正确的递增、递减、取值、成员存取等操作。正确的操作是指:递增时指向下一节点,递减时指向上一节点,取值时取的是节点的数据值,成员取用的是节点的成员。

由于 STL list 是一个双向链表(double linked-list),迭代器必须具备前移、后移的能力,所以 list 提供的是 Bidirectional Iterators。

4.1begin

begin() 获取的是首元素的迭代器,根据上图,直接返回头节点的下一位置即可。

/// @brief 返回指向 forward_list 首元素的迭代器
/// @return 指向首元素的迭代器
iterator begin() 
{
    // 根据节点指针构造迭代器
    return iterator(_node->_next);
}

// const 版本供 const 容器使用
const_iterator begin() const
{
    return const_iterator(_node->_next);
}

4.2end

end() 获取的是最后一个元素下一个位置的迭代器,根据上图就是 _node 所指向的节点。

/// @brief 返回指向 forward_list 末元素后一元素的迭代器
/// @return 指向最后元素下一位置的迭代器
iterator end() 
{
    // 调用 iterator 构造函数
    return iterator(_node);
}

const_iterator end() const 
{
    return const_iterator(_node);
}

5.元素访问

5.1front

front() 获取容器首元素的引用,调用 begin() 得到首元素的迭代器,再解引用即可。

因为 forward_list 的迭代器只能单向移动,故不能快速获得链表中最后一个节点。

/// @brief 返回容器首元素的引用
/// @return 首元素的引用
reference front() 
{
    return *begin();
}

// 与上面唯一不同就是用于 const 容器
const_reference front() const
{
    return *begin();
}

6.容量

6.1empty

begin() 和 end() 指向相同,说明链表此时只有一个头节点,链表为空。

/// @brief 检查容器是否无元素
/// @return 若容器为空则为 true,否则为 false
bool empty() const 
{
    return begin() == end();
}

7.修改器

7.1insert_after

根据 STL 的习惯,插入操作会将新元素插入于指定位置之前,而非之后。然而 forward_list 是一个单向链表,它没有任何方便的方法可以定位出前一个位置,它必须从头找。因此,forward_list 中提供的插入操作,是插入在指定位置之后。

下图为:只有 0、1 两个元素的单链表,在 0 之后插入元素值为 2 的节点的示意图。

插入的过程非常简单:

1.创建一个要插入的节点

2.插入节点的 _next 指向 pos 后一位置的节点

3.pos 的 _next 指向要插入的节点

/// @brief 在容器中的指定位置后插入元素
/// @param pos 内容将插入到其后的迭代器
/// @param val 要插入的元素值
/// @return 指向被插入元素的迭代器
iterator insert_after(iterator pos, const_reference val)
{
    forward_list_node* tmp = new forward_list_node(val);	// 创建要插入的节点
    tmp->_next = pos._node->_next;							// (1)
    pos._node->_next = tmp;									// (2)
    return tmp;
}

7.2push_front

push_front 的作用是在容器起始处插入元素。

直接调用 insert_after() 插入就行,需要注意的是,insert_after() 是在给定位置之后插入,所以应传入头节点对应的迭代器位置。

/// @brief 头插给定元素 val 到容器起始
/// @param val 要头插的元素值
void push_front(const_reference val) 
{
	// end() 返回头节点位置的迭代器,在这之后插入是头插
	insert_after(end(), val);
}

7.3erase_after

下图为:有三个元素 0、1、2 的链表,删除 pos 指向节点之后节点(值为 1)的示意图。

删除的过程非常简单:

1.记录 pos 的下一节点 nextNode

2.将 pos 的 _next 指向 nextNode 的下一个节点

3.delete 释放掉 nextNode 所指向的节点

/// @brief 从容器移除 pos 后面一个元素
/// @param pos 指向要被移除元素前一个元素的迭代器
/// @return 最后移除元素之后的迭代器
iterator erase_after(iterator pos) 
{
    forward_list_node* nextNode = pos._node->_next;		// 记录 pos 指向节点的下一节点
    pos._node->_next = nextNode->_next;					// (1)
    delete (nextNode);
    return (iterator)(pos._node->_next);
}

7.4pop_front

pop_front() 移除容器的首元素,也就是 end() 指向的下一节点。

/// @brief 移除容器首元素
void pop_front() 
{
    erase_after(end());
}

7.5clear

clear() 用于清空容器所有数据,不清理头节点。

要注意 erase_after() 删除给定位置下一个节点,end 的下一个是第一个元素,这样以次删除直到容器为空,即只剩一个头节点。

/// @brief 从容器擦除所有元素
void clear() 
{
    while (!empty())
    {
        erase_after(end());
    }
}

7.6swap

swap() 用来交换两个 forward_list容器,不用交换 forward_list 中每个元素的值,直接交换 _node 指针即可。

/// @brief 将内容与 other 的交换
/// @param other 要与之交换内容的容器
void swap(forward_list& other) 
{
    std::swap(_node, other._node);
}

参考:STL forward_list 模拟实现

posted @ 2023-08-03 07:45  CodeMagicianT  阅读(55)  评论(0编辑  收藏  举报