严蔚敏 《数据结构C语言版》读书笔记

news2024/12/28 20:54:51

目录

第一章 线性表

顺序表 Sequence Table

带头双向循环链表 Linked List

第二章 栈和队列

栈 Stack (顺序存储) 检查括号是否匹配

队列 Queue (链式存储)

循环队列 Circle Queue

第三章 串

模拟实现string.h库函数

第四章 广义表

广义表的实现 利用递归思想

第五章 树

二叉树的构建与遍历 递归思想

第六章 图

图的常用术语

图的存储结构

第七章 查找

二叉排序树的构建:

第八章 内排序

直接插入排序

希尔排序

直接选择排序

堆排序

冒泡排序

快速排序

归并排序

计数排序

基数排序


第一章 线性表

  • 一个线性表是n个数据元素的优先序列
  • 线性表可分为顺序存储结构(数组)和链式存储结构(链表)
  • 链表可分为单链表、循环链表、双向链表

顺序表 Sequence Table

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>

#define CAPACITY 4

typedef int SData;

typedef struct Sequence
{
	SData* data;
	int size;
	int capacity;
}Sequence;

void Init(Sequence* ps)
{
	assert(ps);
	ps->data = (SData*)malloc(sizeof(SData) * CAPACITY);
	ps->size = 0;
	ps->capacity = CAPACITY;
}

void CheckCapacity(Sequence* ps)
{
	if (ps->size == ps->capacity)
	{
		ps->data = realloc(ps->data, sizeof(SData) * ps->size * 2);
		if (ps->data == NULL)
		{
			perror("realloc");
			exit(-1);
		}
		ps->capacity *= 2;
	}
}

bool Empty(Sequence* ps)
{
	assert(ps);
	if (ps->size == 0)
		return true;
	return false;
}

void Push(Sequence* ps, SData x)
{
	assert(ps);
	CheckCapacity(ps);
	ps->data[ps->size] = x;
	++ps->size;
}

void Pop(Sequence* ps)
{
	assert(ps);
	assert(!Empty(ps));
	--ps->size;
}

int Find(Sequence* ps, SData x)
{
	assert(ps);
	int pos = 0;
	for (; pos < ps->size; ++pos)
	{
		if (ps->data[pos] == x)
			return pos;
	}
	return -1;
}

void Insert(Sequence* ps, int pos, SData x)
{
	assert(ps);
	assert(pos <= ps->size);
	CheckCapacity(ps);
	int i = ps->size;
	for (; i >= pos; --i)
	{
		ps->data[i + 1] = ps->data[i];
	}
	ps->data[pos] = x;
	++ps->size;
}

void Erase(Sequence* ps, int pos)
{
	assert(ps);
	assert(!Empty(ps));
	int i = pos;
	for (; i < ps->size - 1; ++i)
	{
		ps->data[i] = ps->data[i + 1];
	}
	--ps->size;
}

void Print(Sequence* ps)
{
	assert(ps);
	int i = 0;
	for (; i < ps->size; ++i)
	{
		printf("%d ", ps->data[i]);
	}
	printf("\n");
}

int main()
{
	Sequence s;
	Init(&s);
	Push(&s, 1);
	Push(&s, 2);
	Push(&s, 3);
	Push(&s, 4);
	Push(&s, 5);
	Print(&s);
	Insert(&s, 0, 10);
	Insert(&s, 3, 20);
	Insert(&s, 7, 30);
	Print(&s);
	Pop(&s);
	Erase(&s, 0);
	Erase(&s, Find(&s, 20));
	Print(&s);
	return 0;
}

带头双向循环链表 Linked List

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>

typedef int LData;

typedef struct List
{
	LData data;
	struct List* next;
	struct List* prev;
}List;

void Init(List* pl)
{
	assert(pl);
	pl->next = pl;
	pl->prev = pl;
}

bool Empty(List* pl)
{
	assert(pl);
	if (pl->next == pl)
		return true;
	return false;
}

void Insert(List* pl, List* pos, LData x)
{
	assert(pl);
	List* new_node = (List*)malloc(sizeof(List));
	new_node->data = x;
	new_node->next = pos;
	new_node->prev = pos->prev;
	pos->prev->next = new_node;
	pos->prev = new_node;
}

void Erase(List* pl, List* pos)
{
	assert(pl);
	assert(pos);
	assert(!Empty(pl));
	List* next = pos->next;
	List* prev = pos->prev;
	prev->next = next;
	next->prev = prev;
	free(pos);
	pos = NULL;
}

void Push(List* pl, LData x)
{
	assert(pl);
	if (Empty(pl))
	{
		List* new_node = (List*)malloc(sizeof(List));
		new_node->data = x;
		new_node->prev = pl;
		new_node->next = pl;
		pl->next = new_node;
		pl->prev = new_node;
	}
	else
	{
		Insert(pl, pl, x);
	}
}

void Pop(List* pl)
{
	assert(pl);
	assert(!Empty(pl));
	Erase(pl, pl->prev);
}

List* Find(List* pl, LData x)
{
	assert(pl);
	List* cur = pl->next;
	while (cur != pl)
	{
		if (cur->data == x)
			return cur;
		cur = cur->next;
	}
	return NULL;
}

void Print(List* pl)
{
	assert(pl);
	List* cur = pl->next;
	while (cur != pl)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

int main()
{
	List list;
	Init(&list);
	Push(&list, 1);
	Push(&list, 2);
	Push(&list, 3);
	Push(&list, 4);
	Print(&list);
	Insert(&list, Find(&list, 1), 10);
	Insert(&list, Find(&list, 2), 20);
	Insert(&list, Find(&list, 4), 40);
	Print(&list);
	Pop(&list);
	Erase(&list, Find(&list, 10));
	Erase(&list, Find(&list, 20));
	Print(&list);
	Print(&list);
	return 0;
}

第二章 栈和队列

  • 栈:先进后出,一般为顺序存储结构
  • 队列:先进先出,一般为链式存储结构

循环队列:

  • 顺序存储结构,设置容量(capacity)及头尾下标(front、rear)
  • push:++rear,    pop:++front,当下标等于capacity时变为0
  • 为区分“满”和“空”,舍弃一个元素空间,使 (rear + 1) % capacity == front 为判“满”

栈 Stack (顺序存储) 检查括号是否匹配

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>

#define CAPACITY 4

typedef char SData;

typedef struct Stack
{
	SData* data;
	int size;
	int capacity;
}Stack;

void Init(Stack* s)
{
	assert(s);
	s->data = (SData*)malloc(sizeof(SData) * CAPACITY);
	s->size = 0;
	s->capacity = CAPACITY;
}

bool Empty(Stack* s)
{
	assert(s);
	if (s->size == 0)
		return true;
	return false;
}

void CheckCapacity(Stack* s)
{
	assert(s);
	if (s->size == s->capacity)
	{
		s->data = (SData*)realloc(s->data, sizeof(SData) * s->capacity * 2);
		s->capacity *= 2;
	}
}

void Push(Stack* s, SData x)
{
	assert(s);
	CheckCapacity(s);
	s->data[s->size] = x;
	++s->size;
}

void Pop(Stack* s)
{
	assert(s);
	assert(!Empty(s));
	--s->size;
}

SData Top(Stack* s)
{
	assert(s);
	return s->data[s->size - 1];
}

int Size(Stack* s)
{
	assert(s);
	return s->size;
}

void CheckBrackets(Stack* s, char* str)
{
	assert(s);
	char* cur = str;
	while (*cur != '\0')
	{
		if (*cur == '{' || *cur == '[' || *cur == '(')
		{
			Push(s, *cur);
		}
		else if (*cur == '}' || *cur == ']' || *cur == ')')
		{
			if (*cur == '}' && Top(s) == '{')
			{
				Pop(s);
			}
			else if (*cur == ']' && Top(s) == '[')
			{
				Pop(s);
			}
			else if (*cur == ')' && Top(s) == '(')
			{
				Pop(s);
			}
			else
			{
				printf("括号不匹配\n");
				return;
			}
		}
		++cur;
	}
	if (Empty(s))
		printf("括号匹配\n");
	else
		printf("括号不匹配\n");
}

int main()
{
	Stack s;
	Init(&s);
	char str[20] = "{[(()*)adf]}";
	CheckBrackets(&s, str);
	return 0;
}

队列 Queue (链式存储)

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>

typedef int LData;

typedef struct Node
{
	LData data;
	struct Node* next;
}Node;

typedef struct Queue
{
	Node* head;
	Node* tail;
	int size;
}Queue;

void Init(Queue* pq)
{
	assert(pq);
	pq->head = (Node*)malloc(sizeof(Node));
	pq->head->next = NULL;
	pq->tail = pq->head;
	pq->size = 0;
}

bool Empty(Queue* pq)
{
	assert(pq);
	if (pq->head->next == NULL)
		return true;
	return false;
}

void Push(Queue* pq, LData x)
{
	assert(pq);
	Node* new_node = (Node*)malloc(sizeof(Node));
	new_node->data = x;
	new_node->next = NULL;
	pq->tail->next = new_node;
	pq->tail = pq->tail->next;
	++pq->size;
}

void Pop(Queue* pq)
{
	assert(pq);
	assert(!Empty(pq));
	Node* tmp = pq->head;
	pq->head = pq->head->next;
	--pq->size;
	free(tmp);
	tmp = NULL;
}

int Size(Queue* pq)
{
	assert(pq);
	return pq->size;
}

LData Front(Queue* pq)
{
	assert(pq);
	assert(!Empty(pq));
	return pq->head->next->data;
}

void Print(Queue* pq)
{
	assert(pq);
	Node* cur = pq->head->next;
	while (cur != NULL)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

int main()
{
	Queue q;
	Init(&q);
	Push(&q, 1);
	Push(&q, 2);
	Push(&q, 3);
	Push(&q, 4);
	Print(&q);
	Push(&q, Front(&q));
	Pop(&q);
	Pop(&q);
	Pop(&q);
	Print(&q);
	printf("size = %d\n", Size(&q));
}

循环队列 Circle Queue

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>

#define CAPACITY 5
typedef int QData;

typedef struct CircleQueue
{
	QData* data;
	int front;
	int tail;
}CircleQueue;

void Init(CircleQueue* pcq)
{
	assert(pcq);
	pcq->data = (QData*)malloc(sizeof(QData) * CAPACITY);
	pcq->front = pcq->tail = 0;
}

bool Empty(CircleQueue* pcq)
{
	assert(pcq);
	if (pcq->front == pcq->tail)
		return true;
	return false;
}

bool Full(CircleQueue* pcq)
{
	assert(pcq);
	if ((pcq->tail + 1) % CAPACITY == pcq->front)
		return true;
	return false;
}

void Push(CircleQueue* pcq, QData x)
{
	assert(pcq);
	assert(!Full(pcq));
	pcq->data[pcq->tail] = x;
	++pcq->tail;
	pcq->tail %= CAPACITY;
}

void Pop(CircleQueue* pcq)
{
	assert(pcq);
	assert(!Empty(pcq));
	++pcq->front;
	pcq->front %= CAPACITY;
}

void Print(CircleQueue* pcq)
{
	assert(pcq);
	int pos = pcq->front;
	while (pos != pcq->tail)
	{
		printf("%d ", pcq->data[pos]);
		++pos;
		pos %= CAPACITY;
	}
	printf("\n");
}

int main()
{
	CircleQueue cq;
	Init(&cq);
	Push(&cq, 1);
	Push(&cq, 2);
	Push(&cq, 3);
	Print(&cq);
	Pop(&cq);
	Pop(&cq);
	Print(&cq);
	Push(&cq, 1);
	Push(&cq, 2);
	Push(&cq, 3);
	Print(&cq);
	return 0;
}

第三章 串

  • 串是由零个或多个字符组成的顺序存储有限序列
  • 串的应用一般包含于<string.h>头文件,常用函数有strlen strcpy strcat strcmp

模拟实现string.h库函数

#include <stdio.h>
#include <assert.h>
#include <stdbool.h>

int MyStrlen(char* str)
{
	assert(str);
	int len = 0;
	char* cur = str;
	while (cur[len++] != '\0'){}
	return len - 1;
}

char* MyStrcpy(char* des, const char* src)
{
	assert(des);
	assert(src);
	char* tmp = des;
	while (*tmp++ = *src++) {}
	return des;
}

char* MyStrcat(char* des, const char* src)
{
	assert(des);
	assert(src);
	char* tmp = des;
	if (des == src)
	{
		int i = 0;
		int len = MyStrlen(src);
		while (*tmp++) {}
		--tmp;
		while (i < len)
		{
			*tmp++ = *src++;
			++i;
		}
		*tmp = '\0';
	}
	else
	{
		while (*tmp++) {}
		--tmp;
		while (*tmp++ = *src++) {}
	}
	return des;
}

int MyStrcmp(const char* str1, const char* str2)
{
	assert(str1);
	assert(str2);
	while (*str1 == *str2)
	{
		if (*str1 == '\0')
			return 0;
		++str1;
		++str2;
	}
	return -(*str1 - *str2);
}

char* MyStrstr(const char* str1, const char* str2)
{
	assert(str1 && str2);
	char* cur = str1;
	while (*cur)
	{
		char* s1 = cur;
		char* s2 = str2;
		while (*s1 && *s2 && *s1 == *s2)
		{
			++s1;
			++s2;
		}
		if (!*s2)
			return cur;
		if (!*s1)
			return NULL;
		++cur;
	}
	return NULL;
}

int main()
{
	char str1[20] = "qwerdf";
	printf("strlen = %d\n", MyStrlen(str1));
	char str2[40];
	printf("str2 strcpy str1 = %s\n", MyStrcpy(str2, str1));
	printf("str2 strcat str1 = %s\n", MyStrcat(str2, str1));
	printf("str2 strcat str2 = %s\n", MyStrcat(str2, str2));
	char str3[40];
	printf("str3 strcmp str2 = %d\n", MyStrcmp(MyStrcpy(str3, str2), str2));
	printf("str1 strstr df = %s\n", MyStrstr(str1, "df"));

	return 0;
}

第四章 广义表

  • 广义表中的数据元素可以具有不同的结构(原子或列表),是链式存储结构
  • 广义表的第一个元素为表头,其余元素为表尾
  • 每个节点可分为表结点或原子节点,用标志位区分
  • 表结点包含标志域和指针域(头尾指针),原子节点包含标志域和值域
  • 广义表的深度为括号的重数,空表的深度为1,例如X = ((), (e), (a, (b,c,d)))的深度为3
  • 广义表的长度为包含的数据元素个数(一个子表只算一个元素)

广义表的实现 利用递归思想

代码转载于:广义表详解(C语言版)_红心火柴的博客-CSDN博客_广义表 c语言

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <malloc.h>

#define AtomType int	//原子类型
typedef enum
{
	HEAD,		//表头结点
	ATOM,		//原子结点
	CHILDLIST	//子表
}ElemTag;

//广义表结点
typedef struct GLNode
{
	ElemTag tag;
	union		//联合体
	{
		AtomType atom;
		struct GLNode* hp;	//表头指针
	};
	struct GLNode* tp;		//表尾指针
}GLNode;

typedef GLNode* GenList;	//广义表

void InitGenList(GenList& gl);
void CreateGenList(GenList& gl, char* str);
bool Sever(char* sub, char* hsub);

char* GetGenListStr(GenList gl, int type);
char* GetHead(GenList gl);
char* GetTail(GenList gl);
char* GetLast(GenList gl);
void ShowGenList(GenList gl);
bool GenListEmpty(GenList gl);
int GenListLength(GenList gl);
int GenListDepth(GenList gl);
void CopyGenList(GenList gl, GenList& T);
void InsertFirstGenList(GenList& gl, char* str);
void DeleteFirstGenList(GenList& gl, char*& str);
void ClearGenList(GenList& gl);
void DestroyGenList(GenList& gl);

//广义表初始化
void InitGenList(GenList& gl)
{
	gl = NULL;
}

//创建广义表:根据字符串创建
void CreateGenList(GenList& gl, char* str)
{
	int n = strlen(str);
	char* sub = (char*)malloc(sizeof(char) * (n - 2));	//存储表内元素
	char* hsub = (char*)malloc(sizeof(char) * (n - 2));	//存储表头
	assert(sub && hsub);
	strncpy(sub, str + 1, n - 2);	//除去字符串两端括号
	sub[n - 2] = '\0';
	if (gl == NULL)		//判空
	{
		gl = (GLNode*)malloc(sizeof(GLNode));
		gl->tag = HEAD;		//标记为头结点
		gl->hp = gl->tp = NULL;	//把子表指针和尾指针都置空
	}
	GLNode* p = gl;
	while (strlen(sub) != 0)
	{
		//尾插法,从后面插入结点
		//	1. 创建一个结点
		//  2. 让p所指节点的尾指针指向新建的结点
		//  3. 让p指向新建节点
		p = p->tp = (GLNode*)malloc(sizeof(GLNode));
		p->hp = p->tp = NULL;
		if (Sever(sub, hsub))	//Sever函数分离表头,并将表头存入hsub中
		{
			if (hsub[0] == '(')
			{
				//创建子表节点
				p->tag = CHILDLIST;	//标记子表
				CreateGenList(p->hp, hsub);
			}
			else
			{
				p->tag = ATOM;	//设置原子标记
				p->atom = atoi(hsub);	//将表头字符串转换成整型并赋值
			}
		}
	}
}

//分离表头,将sub中的表头存入到hsub中
bool Sever(char* sub, char* hsub)
{
	if (*sub == '\0' || strcmp(sub, "()") == 0)
	{
		hsub[0] = '\0';
		return true;
	}
	int n = strlen(sub);
	int i = 0;
	char ch = sub[0];
	int k = 0;	//表示括号信息
	while (i < n && (ch != ',' || k != 0))
	{
		if (ch == '(')
			++k;
		else if (ch == ')')
			--k;
		++i;
		ch = sub[i];
	}
	if (i < n)
	{
		//在 i 位置处截断
		sub[i] = '\0';
		strcpy(hsub, sub);			//将取得的表头放入hsub中
		strcpy(sub, sub + i + 1);	//更新sub:此时的sub是去掉表头hsub
	}
	else if (k != 0)		//判断内部括号是否匹配
		return false;
	else	// i >= n, 整个sub都是表头
	{
		strcpy(hsub, sub);
		sub[0] = '\0';
	}
	return true;
}

//将整数转换成字符串
void NumToStr(int num, char* str, int& i)
{
	char tmp[25];
	_itoa(num, tmp, 10);
	for (unsigned j = 0; j < strlen(tmp); ++j)
		str[i++] = tmp[j];
}

void GetGenList(GenList gl, char* str, int& i)
{
	GLNode* p = gl->tp;
	while (p != NULL)
	{
		if (p->tag == ATOM)
		{
			NumToStr(p->atom, str, i);
			if (p->tp != NULL)
				str[i++] = ',';
			p = p->tp;
		}
		else if (p->tag == CHILDLIST)
		{
			str[i++] = '(';
			GetGenList(p->hp, str, i);
			str[i++] = ')';
			if (p->tp != NULL)
				str[i++] = ',';
			p = p->tp;
		}
	}
}

//将广义表转化成字符串形式
char* GetGenListStr(GenList gl, int type)
{
	int i = 0;
	char* str = (char*)malloc(sizeof(char) * 1000);
	if (type == 0)
	{
		NumToStr(gl->atom, str, i);
	}
	else
	{
		GetGenList(gl, str, i);
	}
	str[i] = '\0';
	return str;
}

//取首元素
char* GetHead(GenList gl)
{
	if (gl->tp->tag == ATOM)
		return GetGenListStr(gl->tp, 0);
	else
		return GetGenListStr(gl->hp, 1);
}

char* GetTail(GenList gl)
{
	GLNode* p = gl->tp;
	if (p->tp != NULL)
		return GetGenListStr(p, 1);
	return NULL;
}

//取最后一个元素
char* GetLast(GenList gl)
{
	GLNode* p = gl->tp;
	while (p->tp != NULL)
		p = p->tp;
	if (p->tag == ATOM)
		return GetGenListStr(p, 0);
	else
		return GetGenListStr(p->hp, 1);
}

//打印广义表
void ShowGenList(GenList gl)
{
	printf("(%s)", GetGenListStr(gl, 1));
}

//判空
bool GenListEmpty(GenList gl)
{
	return gl->tp == NULL;
}

int GenListLength(GenList gl)
{
	int length = 0;
	GLNode* p = gl->tp;
	while (p != NULL)
	{
		++length;
		p = p->tp;
	}
	return length;
}

int GenListDepth(GenList gl)
{
	if (gl->tp == NULL)
		return 1;
	GLNode* p = gl->tp;
	int maxdepth = 0;
	int dep;
	while (p != NULL)
	{
		if (p != NULL)
		{
			if (p->tag == CHILDLIST)
			{
				dep = GenListDepth(p->hp);
				if (dep > maxdepth)
					maxdepth = dep;
			}
		}
		p = p->tp;
	}
	return maxdepth + 1;
}

//广义表复制
void CopyGenList(GenList gl, GenList& T)
{
	if (gl == NULL)
		return;
	if (T != NULL)
		DestroyGenList(T);
	T = (GLNode*)malloc(sizeof(GLNode));
	T->tag = gl->tag;
	T->hp = gl->hp;
	T->tp = gl->tp;
	GLNode* p = gl->tp;
	GLNode* q = T;
	while (p != NULL)
	{
		q = q->tp = (GLNode*)malloc(sizeof(GLNode));
		q->tag = p->tag;
		q->hp = q->tp = NULL;
		if (p->tag == ATOM)
		{
			q->atom = p->atom;
			p = p->tp;
		}
		else if (p->tag == CHILDLIST)
		{
			CopyGenList(p->hp, q->hp);
			p = p->tp;
		}
	}
}

//插入元素str
void InsertFirstGenList(GenList& gl, char* str)
{
	GenList t;
	InitGenList(t);
	CreateGenList(t, str);
	GLNode* p = t->tp;
	while (p->tp != NULL)
		p = p->tp;
	p->tp = gl->tp;
	gl->tp = t->tp;
	free(t);
}

//删除广义表第一个位置的元素
void DeleteFirstGenList(GenList& gl, char*& str)
{
	GenList t;
	InitGenList(t);
	t = gl->tp;
	gl->tp = gl->tp->tp;
	if (t->tag == CHILDLIST)
	{
		str = GetGenListStr(t->hp, 1);
		DestroyGenList(t->hp);
	}
	else if (t->tag == ATOM)
	{
		str = GetGenListStr(t, 0);
	}
	free(t);
}

//清空广义表
void ClearGenList(GenList& gl)
{
	GLNode* p = gl->tp;
	while (p != NULL)
	{
		if (p->tag == ATOM)
		{
			gl->tp = p->tp;
			free(p);
			p = gl->tp;
		}
		else if (p->tag == CHILDLIST)
		{
			ClearGenList(p->hp);
			p = p->tp;
		}
	}
}

//销毁
void DestroyGenList(GenList& gl)
{
	ClearGenList(gl);
	free(gl);
	gl = NULL;
}

//测试
int main()
{
	GenList gl;
	InitGenList(gl);

	char ga[30] = "(1,2,3)";
	char gb[30] = "(1,(2,3))";
	char gc[30] = "(1,(2,3),4)";
	char gd[30] = "((1,2),3)";
	char ge[30] = "((1,2,3))";
	char gf[30] = "()";
	char gg[30] = "(1,(2,(3,(10,20),4),5),6)";
	char gh[30] = "((((1,2),1),1),6,1)";

	CreateGenList(gl, gg);
	ShowGenList(gl);
	printf("\n");
	int length = GenListLength(gl);
	printf("length = %d\n", length);

	int depth = GenListDepth(gl);
	printf("depth = %d\n", depth);

	GenList T;
	InitGenList(T);
	printf("----------------------------\n");
	printf("复制:");
	CopyGenList(gl, T);
	ShowGenList(T);
	printf("\n");

	printf("----------------------------\n");
	printf("插入前:");
	ShowGenList(gl);
	printf("\n");
	InsertFirstGenList(gl, ga);
	printf("插入后:");
	ShowGenList(gl);
	printf("\n");
	printf("----------------------------\n");
	char* str;
	printf("删除前:");
	ShowGenList(gl);
	printf("\n");
	DeleteFirstGenList(gl, str);
	printf("删除后:");
	ShowGenList(gl);
	printf("\n");
	printf("删除的首元素为:%s\n", str);

	printf("----------------------------\n");
	ShowGenList(gl);
	printf("\n");
	printf("头元素为:%s\n", GetHead(gl));
	printf("尾元素为:%s\n", GetTail(gl));

	DestroyGenList(gl);
	return 0;
}

第五章 树

  • 树是有n个节点的有限集,任意一棵非空树有且仅有一个根节点
  • 拥有的分支数称为节点的度,度为0的节点为叶子节点
  • 森林是若干棵互补相交的树的集合

二叉树:

  • 二叉树每个节点的度 ≤ 2,分左右孩子
  • 二叉树的第 i 层最多有 2^(i-1) 个节点
  • 当深度为 k 的二叉树有 2^k - 1 个节点时是满二叉树
  • 当k-1层是满的,第k层是连续的节点时是完全二叉树
  • 具有 n 个节点的满二叉树的深度 h = log2(n+1)
  • 任一二叉树,设叶节点数为n0,度为2的节点数为n2,则 n0 = n2 + 1
  • 遍历二叉树分为先序遍历(根左右)、中序遍历(左根右)、后序遍历(左右根)、层序遍历
  • 任一具有n个节点的树,其分支有n-1条

 二叉树与森林的转换:

赫夫曼树:

  • 赫夫曼树,又称最优树,是一类带权路径最短的树
  • 路径长度:路径上的分支数目
  • 树的带权路径长度:所有叶子节点的带权路径长度之和

二叉树的构建与遍历 递归思想

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>

typedef char NodeType;

typedef struct BNTree
{
	NodeType data;
	struct BNTree* left;
	struct BNTree* right;
}BNTree;

BNTree* BuyNode(NodeType d)			//开辟一个二叉树节点
{
	BNTree* new_node = (BNTree*)malloc(sizeof(BNTree));
	new_node->data = d;
	new_node->left = new_node->right = NULL;
	return new_node;
}

BNTree* CreateBNTree(NodeType* d, int* pi)		//二叉树创建
{
	if (d[*pi] = '#')
	{
		(*pi)++;
		return NULL;
	}
	BNTree* root = BuyNode(d[(*pi)++]);			//先序构建
	root->left = CreateBNTree(d, pi);
	root->right = CreateBNTree(d, pi);
	return root;
}

void PreOrder(BNTree* root)	//先序遍历,中序遍历和后续遍历都是一样的递归思想
{
	if (root == NULL)
		return;
	printf("%c ", root->data);
	PreOrder(root->left);
	PreOrder(root->right);
}

第六章 图

图是一种抽象数据类型,图的元素被称为顶点,顶点之间可能存在相关关系,一个图G包含一个顶点集V和关系集VR,记为:G = (V, VR)

图的常用术语

1. 有向图、弧

  • 若图G中的两个顶点a、b的关系为有序对<a,b>,<a,b>∈VR,则称<a,b>为有向图G的一条弧
  • 在弧<a,b>中,a是弧尾(初始点),b是弧头(终止点)
  • 有向图是由顶点集和弧集组成的二元集合

2. 无向图、边

  • 若图G中的两个顶点a、b的关系为无序对(a,b),(a,b)∈VR,则称(a,b)为无向图G的一条边
  • 若顶点a、b间有边,则(a,b)表示a、b互为邻接点,边(a,b)依附于顶点a、b

3. 完全图、有向完全图

  • n:顶点的数目
  • e:边或弧的数目
  • 若边数 e = n*(n-1)/2 的无向图,则称为完全图,表示任意两个顶点间都有边
  • 若弧数 e = n*(n-1) 的有向图,则称有向完全图,表示任意两个顶点间都有两条弧

4. 网

  • 弧(边)上加权的图,分为有向网和无向网
  • 故有四种类型的图:有向图、无向图、有向网、无向网

5. 子图

  • 对图G=(V, VR),G1=(V1, VR1),若V1∈V且VR1∈VR,则G1是G的子图
  • 下例中G1、G2、G3都是G的子图,G4不是G的子图

6. 度、出度、入度

  • 度D(TD):图中与某顶点相关的边(弧)的数目,称为某顶点的度
  • 无向图中某顶点的度表示该顶点有多少个邻接点
  • 出度OD:有向图中以某顶点为弧尾的弧的数目
  • 入度ID:有向图中以某顶点为弧头的弧的数目
  • 有向图某顶点的度 D = OD + ID

7. 连通性

  • 从顶点vi到顶点vj有路径,则称二者是连通的
  • 连通图:图中任意两个顶点都是连通的
  • 完全图一定是连通图,连通图不一定是完全图
  • 连通分量:无向图的极大连通子图,连通图的连通分量是自己本身
  • 强连通图:有向图中任意两个顶点vi和vj之间,从vi到vj和从vj到vi之间都存在路径
  • 强连通分量:有向图的极大强连通子图,强联通图的强连通分量是自己本身

8. 生成树

  • 一个连通图的生成树是一个极小连通子图
  • 生成树含有图中的全部n个顶点,但有且仅有n-1条边
  • 如果在生成树上再添加一条边,必定构成一个环,不再是树结构
  • 一个有n个顶点的图,如果边数小于n-1,则一定是非连通图,如果多于n-1条边,则一定有环
  • 有n-1条边的图不一定是生成树
  • 一个连通图可以有多颗生成树

图的存储结构

1. 数组表示

  • 用两个数组分别存储数据元素的信息和数据元素之间的关系
  • 顶点数组:用一维数组存储顶点
  • 关系数组:用二维数组存储顶点之间的关系

(1)无向图

  • 顶点数组vexs:V = {v0, v1, v2, v3}
  • 关系数组(邻接矩阵arcs):1表示顶点之间有关系,0表示顶点之间无关系
  • 邻接矩阵arcs:VR = {(v0,v2), (v0,v3), (v2,v3)}
  • 无向图的邻接矩阵是对称矩阵
  • 顶点 vi 的度为第 i 行或第 i 列的和

(2)有向图

  • 顶点数组vexs:V = {v0, v1, v2, v3}
  • 邻接矩阵arcs:VR = {<v0,v2>, <v2,v0>, <v2,v3>, <v3,v0>}
  • 有向图的邻接矩阵不一定是对称矩阵
  • 顶点 vi 的度为第 i 行和第 i 列的和,即 D = OD + ID

2. 邻接表

顺序+链式存储结构,通过头结点数组保存顶点信息,用单链表保存顶点之间的关系

无向图:

  • 图G的每个顶点建立一个单链表,第 i 个单链表中的结点表示依附于 vi 的边
  • 若有 n 个顶点和 c 条边,则有 n 条单链表和 2*c 个表结点
  • 顶点 vi 的度 = 第 i 个单链表的长度

有向图:

  •  图G中的每个顶点建立一个单链表,第 i 条链表中的表结点值为 j ,表示<vi, vj>
  • 若有 n 个顶点和 c 条弧,则有 n 条单链表和 c 个表结点
  • 顶点 vi 的度 = 第 i 个单链表的长度

3. 十字链表

十字链表是针对有向图设计的邻接表和逆邻接表的组合

  • 每个顶点有一个顶点结点,顶点结点包括:data(顶点信息)、first_in(指向以该顶点为弧头的第一条弧)、first_out(指向以该顶点为弧尾的第一条弧)
  • 每条弧有一个弧结点,若 vi 到 vj 有弧,则弧结点包括:tail_vec(弧尾位置)、head_vec(弧头位置)、h_link(指向下一条弧头(vi)相同的弧)、t_link(指向下一条弧尾(vi)相同的弧)
  • n条弧就有n个链表结点

4. 邻接多重表(无向图)

  • 每个顶点有一个头结点,顶点节点包括:data(顶点信息)、first_edge(指向第一条依附于该顶点的边)
  • 每一条边都有一个表结点,表结点包括:mark(标志域,标记该边是否被搜索过)、vi / vj(依附于该边的两个顶点位置)、li(指向下一条依附于顶点vi的边)、lj(指向下一条依附于顶点vj的边)
  • n条边就有n个表结点

第七章 查找

折半查找(二分查找):针对有序数列,时间复杂度为O(log2N)

二叉排序树(二叉查找树):若左子树非空,则左子树上所有杰点都小于它的根结点;若右子树非空,则右子树上所有结点都大于它的根结点

平衡二叉树(AVL树):左子树和右子树的深度之差的绝对值(平衡因子)不超过1,某结点的平衡因子为其左子树的深度减去右子树的深度

二叉排序树的构建:

#include <iostream>
#include <queue>
using namespace std;

typedef struct Node
{
	int data;
	struct Node* left;
	struct Node* right;
}Node;

void BuyNode(Node*& t, int x)
{
	t = new Node;
	t->data = x;
	t->left = t->right = NULL;
}

void CreateBTree(Node*& t, int x)
{
	if (t == NULL)
	{
		BuyNode(t, x);
		return;
	}
	if (x < t->data)
		CreateBTree(t->left, x);
	else
		CreateBTree(t->right, x);
}

void PrintBTree(Node* t)
{
	Node* p = t;
	queue<Node*> q;
	q.push(p);
	while (!q.empty())
	{
		p = q.front();
		q.pop();
		cout << p->data << ' ';
		if (p->left != NULL)
			q.push(p->left);
		if (p->right != NULL)
			q.push(p->right);
	}
}

int main()
{
	int n;
	while (cin >> n)
	{
		Node* t = NULL;
		for (int i = 0; i < n; ++i)
		{
			int a;
			cin >> a;
			CreateBTree(t, a);
		}
		PrintBTree(t);
		cout << endl;
	}
}

B-树(平衡多路查找树):常应用于文件系统,一棵m阶的B-树,或为空树,或遵循下列条件:

  • 树中的每个结点最多有m棵子树
  • 若根结点不是叶子结点,则至少有两棵子树
  • 除根结点外,所有非终端结点至少有 m/2 棵
  • 所有非终端结点包含如下信息:(n, A0,K1, A1, K2, A2......Kn, An), Ki为关键码
  • 所有叶子结点都出现在同一层次上,且不带信息

要在如上4阶B-树中查找45这个树的步骤:

  • 首先从根结点开始,45 > 35,往右子树走,到结点c
  • 结点c有两个关键字,43 < 45 < 78,故往中间的子树走,到结点g
  • 结点g有三个关键字,45 < 47,往最左边子树走,到叶子结点F,故45不在B-树中

B-树的插入:

  • 用查找的方法找出关键字的插入位置,若查找到了这个数,则直接返回
  • 判断插入位置的结点是否满足 n <= m-1 ,若满足则直接插入,若不满足则进行分裂
  • 分裂规则是左右两边的数分裂成不同结点,中间的数移到父结点,再检查父结点是否满足阶数规则

B-树的删除:

  • 先直接删除,再视情况进行调整,以满足阶数规则

B+树

一棵m阶的B+树与m阶的B-树的差异:

  • 有n棵子树的结点有n个关键字
  • 所有关键字信息都在叶子节点中,叶子结点本身依关键字顺序存储
  • 所有非终端结点都是索引部分,结点中只含有其子树(根结点)最大(或最小)的关键字
  • B+树的查找,不管成功与否,每次都是从根到叶子结点的路径

哈希表:若结构中存在关键字和K相等的记录,则必定在 f(K) 的存储位置上,由此不需比较便可直接取得所查记录,因此这个对应关系 f 为哈希函数,这个思维建立的表为哈希表

第八章 内排序

直接插入排序

void InsertSort(int* arr, int n)
{
	int i;
	for (i = 0; i < n - 1; ++i)
	{
		int end = i;
		int tmp = arr[end + 1];
		while (end >= 0 && tmp < arr[end])
		{
			arr[end + 1] = arr[end];
			--end;
		}
		arr[end + 1] = tmp;
	}
}

希尔排序

第一步:预排序

第二部:直接插入排序

void ShellSort(int* arr, int n)
{
	int gap = n;
	while (gap > 1)
	{
		gap = gap / 3 + 1;
		int i;
		for (i = 0; i < n - gap; ++i)
		{
			int end = i;
			int tmp = arr[end + gap];
			while (end >= 0 && tmp < arr[end])
			{
				arr[end + gap] = arr[end];
				end -= gap;
			}
			arr[end + gap] = tmp;
		}
	}
}

直接选择排序

void SelectSort(int* arr, int n)
{
	int begin = 0;
	int end = n - 1;
	while (begin < end)
	{
		int mini = begin;
		int maxi = end;
		int i;
		for (i = begin + 1; i <= end; ++i)
		{
			if (arr[i] > arr[maxi])
				maxi = i;
			else if (arr[i] < arr[mini])
				mini = i;
		}
		int tmp = arr[begin];
		arr[begin] = arr[mini];
		arr[mini] = tmp;
		if (maxi == begin)
			maxi = mini;
		int temp = arr[end];
		arr[end] = arr[maxi];
		arr[maxi] = temp;
		++begin;
		--end;
	}
}

堆排序

//建立大根堆——升序
//建立小根堆——降序
void AdjustDown(int* arr, int size, int parent)
{
	int child = parent * 2 + 1;
	while (child < size)
	{
		if (child + 1 < size && arr[child + 1] > arr[child])
			++child;
		if (arr[parent] < arr[child])
		{
			int tmp = arr[parent];
			arr[parent] = arr[child];
			arr[child] = tmp;
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

void HeapSort(int* arr, int n)
{
	int parent = (n - 1 - 1) / 2;
	while (parent >= 0)
	{
		AdjustDown(arr, n, parent);
		--parent;
	}
	while (n > 1)
	{
		int tmp = arr[0];
		arr[0] = arr[n - 1];
		arr[n - 1] = tmp;
		AdjustDown(arr, n - 1, 0);
		--n;
	}
}

冒泡排序

void BubbleSort(int* arr, int n)
{
	int i, j;
	for (i = 0; i < n; ++i)
	{
		int flag = 0;
		for (j = 0; j < n - 1 - i; ++j)
		{
			if (arr[j + 1] < arr[j])
			{
				int tmp = arr[j + 1];
				arr[j + 1] = arr[j];
				arr[j] = tmp;
				flag = 1;
			}
		}
		if (flag == 0)
			break;
	}
}

快速排序

第一步:找到一个值key(默认将key值设为数组第一个元素)

第二步:用循环将比key值小的放key值左边,比key值大的放key值右边

第三步:用递归(二叉树的分治算法),将key值的左右分为两个子树进行排序

//三数取1,取中位数
int GetMidIndex(int* arr, int begin, int end)
{
	int midi = (begin + end) / 2;
	if (arr[begin] < arr[end])
	{
		if (arr[midi] < arr[begin])
			return begin;
		else if (arr[midi] < arr[end])
			return midi;
		else
			return end;
	}
	else
	{
		if (arr[midi] > arr[begin])
			return begin;
		else if (arr[midi] > arr[end])
			return midi;
		else
			return end;
	}
}

int PtrPartSort(int* arr, int begin, int end)
{
	int keyi = begin;
	int pre = begin;
	int cur = begin + 1;
	int midi = GetMidIndex(arr, begin, end);
	int tmp = arr[keyi];
	arr[keyi] = arr[midi];
	arr[midi] = tmp;
	while (cur <= end)
	{
		if (arr[cur] < arr[keyi] && pre++ != cur)
		{
			int temp = arr[cur];
			arr[cur] = arr[pre];
			arr[pre] = temp;
		}
		++cur;
	}
	int t = arr[keyi];
	arr[keyi] = arr[pre];
	arr[pre] = t;
	return pre;
}

void QuickSort(int* arr, int begin, int end)
{
	if (begin >= end)
		return;
	int keyi = PtrPartSort(arr, begin, end);
	if (begin - end > 10)
	{
		QuickSort(arr, begin, keyi - 1);
		QuickSort(arr, keyi + 1, end);
	}
	else
	{
		InsertSort(arr + begin, end - begin + 1);
	}
}

归并排序

void _MergeSort(int* arr, int begin, int end, int* tmp)
{
	if (begin >= end)
		return;
	int mid = (begin + end) / 2;
	_MergeSort(arr, begin, mid, tmp);
	_MergeSort(arr, mid + 1, end, tmp);
	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;
	int i = begin;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (arr[begin1] < arr[begin2])
			tmp[i++] = arr[begin1++];
		if (arr[begin1] > arr[begin2])
			tmp[i++] = arr[begin2++];
	}
	while (begin1 <= end1)
	{
		tmp[i++] = arr[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[i++] = arr[begin2++];
	}
	memcpy(arr + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}

void MergeSort(int* arr, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc");
		exit(-1);
	}
	_MergeSort(arr, 0, n - 1, tmp);
	free(tmp);
}

计数排序

void NumSort(int* arr, int n)
{
	int min = arr[0];
	int max = arr[0];
	int i;
	for (i = 1; i < n; ++i)
	{
		if (arr[i] < min)
			min = arr[i];
		if (arr[i] > max)
			max = arr[i];
	}
	int* count = (int*)malloc(sizeof(int) * (max - min + 1));
	memset(count, 0, sizeof(int) * (max - min + 1));
	for (i = 0; i < n; ++i)
	{
		count[arr[i] - min]++;
	}
	int j = 0;
	for (i = 0; i <= max - min; ++i)
	{
		while (count[i]--)
		{
			arr[j++] = min + i;
		}
	}
	free(count);
	count = NULL;
}

基数排序

#include <iostream>
#include <queue>
#include <iomanip>
using namespace std;

#define K 3
#define RADIX 10

int GetKey(int value, int k)
{
	int key = 0;
	while (k >= 0)
	{
		key = value % 10;
		value /= 10;
		--k;
	}
	return key;
}

queue<int> Q[RADIX];	//定义基数

void Distribute(int* arr, int left, int right, int k)
{
	for (int i = left; i < right; ++i)
	{
		int key = GetKey(arr[i], k);
		Q[key].push(arr[i]);
	}
}

void Collect(int* arr)
{
	int k = 0;
	for (int i = 0; i < RADIX; ++i)
	{
		while (!Q[i].empty())
		{
			arr[k++] = Q[i].front();
			Q[i].pop();
		}
	}
}

void RadixSort(int* arr, int left, int right)
{
	for (int i = 0; i < K; ++i)
	{
		Distribute(arr, left, right, i);
		Collect(arr);
	}
}

void PrintArr(int* arr, int n)
{
	int i = 0;
	for (; i < n; ++i)
		printf("%d ", arr[i]);
	printf("\n");
}

int main()
{
	int arr[20] = { 11, 333, 521, 71, 19, 2, 432, 61, 38, 120, 0 };

	RadixSort(arr, 0, 11);
	PrintArr(arr, 11);

	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/126543.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2 面向对象编程

面向对象编程(OOP) **面向对象的程序(oop)**是由对象组成的&#xff0c; 每个对象包含对用户公开的特定功能部分和隐藏的实现部分。 传统的结构化程序设计通过**设计一系列的过程&#xff08;即算法&#xff09;**来求解问题。一旦确定了这些过程&#xff0c; 就要开始考虑存…

设备通过发放批量自注册场景体验【玩转华为云】

创建实例 首先&#xff0c;根据提示创建实例 实例名称实例描述 创建成功 可以看到&#xff0c;当前实例已经在运行中了 设备发放 在左侧列表中点击“设备发放” 功能简介 您可以通过“设备发放”轻松管理跨多区域&#xff0c;海量设备的发放工作&#xff0c;实现单点发放管…

.Net WebApi— SwaggerUI配置

最近新公司用了特别老的技术【Web 服务 .asmx文件 做WebService服务】&#xff0c;而WebApi早就流行四五年了&#xff1b; 实在太过于简陋&#xff0c;关键其他系统对接的同事&#xff0c;经常说对接不上&#xff0c;如果接口过多确实不方便接口管理&#xff0c;所以最终决定重…

基于Amlogic 安卓9.0, 驱动简说(一):字符设备驱动,手动创建设备

文章目录一、前言二、系列文章三、解析&#xff1a;完整源码1. helloworld_amlogic_char_driver.c2. Makefile四、编译执行4.1 编译4.2 执行&#xff08;1&#xff09;部署&#xff08;2&#xff09;加载ko文件&#xff08;3&#xff09;查看结果&#xff08;4&#xff09;是否…

android 皮肤包换肤之Resources加载(一)

Android 换肤之资源(Resources)加载(一) 本系列计划3篇: Android 换肤之资源(Resources)加载(一) — 本篇setContentView() / LayoutInflater源码分析(二)换肤框架搭建(三) 看完本篇你可以学会什么? Resources在什么时候被解析并加载的 Application#ResourcesActivity#Reso…

【Python黑帽子】——搭建TCP端口扫描器

作者名&#xff1a;Demo不是emo 主页面链接&#xff1a;主页传送门 创作初心&#xff1a;舞台再大&#xff0c;你不上台&#xff0c;永远是观众&#xff0c;没人会关心你努不努力&#xff0c;摔的痛不痛&#xff0c;他们只会看你最后站在什么位置&#xff0c;然后羡慕或鄙夷座…

计算1到n的和(不用循环且逐步限制条件)

目录 一、题目简单描述 二、递归实现 1、if…else… 2、三目运算符 &#xff1f;&#xff1a; 3、逻辑与操作符 && 三、公式实现 四、C调用构造函数累加法 注&#xff1a;满足题目要求的解法有递归实现的第三种、公式实现、C调用构造函数累加法三种方法、 一、题目简…

死锁的成因以及解决方案

&#x1f388;专栏链接:多线程相关知识详解 目录 一.什么是死锁以及死锁的成因 Ⅰ.一个线程一把锁 Ⅱ.两个线程两把锁 Ⅲ.多个线程多把锁 二.死锁的解决方案 一.什么是死锁以及死锁的成因 死锁是一个线程加上锁了之后,解不开了 在多线程编程中&#xff0c;我们为了防止多…

【微服务】3、NACOS 的使用

&#x1f516; Eureka 可以做注册中心【https://github.com/Netflix/eureka】 &#x1f516; 但它的功能比较少&#xff0c;仅仅注册中心 &#x1f516; nacos 也可做注册中心&#xff0c;且功能更加丰富【https://nacos.io/】 一、了解 Nacos ✏️ Nacos 是阿里巴巴的产品&am…

【Python】PyQt拖动控件对齐到网格

实现如下需求&#xff1a; 在PyQt界面上有一个控件&#xff0c;实现其可任意拖动&#xff0c;且鼠标释放时自动对齐到网格。 目录1.控件任意拖动并对齐到网格2.进阶&#xff1a;双击控件使其移动到其他网格1.控件任意拖动并对齐到网格 如下按钮(尺寸100100)&#xff0c;可任意…

【K3s】第11篇 解决“1 Preemption is not helpful for scheduling”问题

目录 1、遇到问题 2、问题解决 1、遇到问题 sudo kubectl get pods -A sudo kubectl describe pods coredns-b96499967-q5lzw -n kube-system Events: Type Reason Age From Message ---- ------ ---- ---- …

YXC | ADAS自动驾驶四大模块选用晶振有何要求

近几年无人驾驶汽车&#xff08;ADAS&#xff09;热度非常高&#xff0c;不少汽企巨头纷纷入局&#xff0c;那么无人驾驶汽车需具备什么硬件设备呢&#xff1f; 自动驾驶汽车依靠人工智能&#xff08;AI&#xff09;、视觉计算、监控系统模块、雷达测距系统模块、和GPS全球定位…

SpringBoot 这两个配置文件有什么区别?

本文讲解了关于 SpringBoot 自动装配的两个配置文件spring.factories 和 spring-autoconfigure-metadata.properties有什么区别&#xff1f;点击上方“后端开发技术”&#xff0c;选择“设为星标” &#xff0c;优质资源及时送达读过上一片文章你可能会发现&#xff0c;在自动装…

第二个脚本——自动登录学习通

目录 本篇主要内容&#xff1a; 详细步骤&#xff1a; 第一步&#xff1a;对登入页面进行分析 第二步&#xff1a;模拟点击&#xff0c;表单填写和多边框操作原理介绍 模拟点击 表单填写 操作多选框: 第三步&#xff0c;实现自动登录 完整代码&#xff1a; 本篇主要内…

日百万流量网站励志一生被K

我是卢松松&#xff0c;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; 曾经每天小百万IP的网站、Z-blog流量最大的网址之一&#xff0c;励志一生这两周被百度K了&#xff0c;流量瞬间没有了&#xff0c;联盟广告收入估计日落千丈。这个网站有多牛可能很多人不清楚&#…

Linux内存管理:NUMA技术详解(非一致内存访问架构)

一.背景 所谓物理内存&#xff0c;就是安装在机器上的&#xff0c;实打实的内存设备&#xff08;不包括硬件cache&#xff09;&#xff0c;被CPU通过总线访问。在多核系统中&#xff0c;如果物理内存对所有CPU来说没有区别&#xff0c;每个CPU访问内存的方式也一样&#xff0c…

基于springboot+jpa 实现多租户动态切换多数据源 - 使用Flyway实现多数据源数据库脚本管理和迭代更新

多租户动态多数据源系列 1、基于springbootjpa 实现多租户动态切换多数据源 - 数据隔离方案选择分库还是分表 2、基于springbootjpa 实现多租户动态切换多数据源 - 基于dynamic-datasource实现多租户动态切换数据源 3、基于springbootjpa 实现多租户动态切换多数据源 - 使用Fl…

Kafka Producer Acks机制

Kafka Producer Acks 设置ACK props.put("acks", "all");通过上述代码&#xff0c;配置kafka生产者发送消息后&#xff0c;是否等待Broker的回执信息。在集群环境下&#xff0c;该配置是kafka保证数据不丢的重要的参数之一&#xff0c;今天来学习一下&…

深入理解Elasticsearch分片

了解分片的基本原理&#xff0c;对Elasticsearch性能调优有帮助。 关系梳理 ES底层使用的是Lucene库&#xff0c;ES的分片&#xff08;shard &#xff09;是Lucene的索引&#xff0c;ES的索引是分片的集合&#xff0c;Lucene的索引是由多个段&#xff08;segment&#xff09;…

青岛OJ如何导入题库详细图示

打开你的后台管理 找到问题位置 增加题目是可以编辑题目&#xff0c;导入数据。 导入导出是用题目和数据直接导入的。 这个ID的话就是题目ID不能设置一样的 然后题目输入输出就都不说了 按照格式就可以了&#xff0c;这里说一下Tag是标签&#xff0c;每次都要设置&#xff…