데이터 구조에 대한 0 기반 소개(C 언어로 구현됨)

서문: 데이터 구조는 C++ 학습에서 어려운 부분으로 학습자의 더 높은 요구 사항에 해당합니다. 기초가 탄탄하지 않은 경우 C 언어의 포인터 및 구조 학습에 집중하는 것이 좋습니다. 그러면 하늘 높이 건물이 떠오를 것입니다. 땅에서.

목차:

 

1. 연결리스트

1) 단일 연결 리스트의 대략적인 구조 구현

2) 단일 연결 목록에 대해 생각하기 (그런 다음 연결 목록을 찾아 연결 목록의 끝을 결정)

3) 단일 연결 리스트의 프로그램 구현 및 소스코드 설명

1) 연결리스트 구현을 위한 전제조건

2) 단일 연결 리스트 생성 및 초기화

3) 단일 연결 리스트 삽입 종료

4) 단일 연결 리스트의 헤드 플러그

5) 단일 연결 리스트의 헤드를 삭제합니다.

6) 단일 연결 리스트의 tail 삭제

7) 단일 연결 리스트에서 요소 찾기

8) 단일 연결 리스트에서 지정된 노드 뒤에 요소 삽입 및 삭제

9) 단일 연결 리스트의 메모리 파괴

2) 양방향 순환 연결 리스트를 주도하기 위한 팁(직접 구현)

2. 큐와 스택

1) 대기열 특성

2) 스택의 특성

3) Linked List를 이용하여 Queue를 구현합니다. (소스코드 및 자세한 설명)

1) 대기열 구조 및 기능 구현 전 준비

2) 대기열 초기화

3) 데이터 입력

4) 데이터 대기열 제거

5) 대기열의 헤드 요소를 가져옵니다. 

6) 대기열의 꼬리 요소를 가져옵니다.

7) 대기열 요소 삭제

4) 스택 코드 구현(다른 아이디어 제공 및 직접 구현)


 

1. 연결리스트

1) 단일 연결 리스트의 대략적인 구조 구현

C언어에서 연결리스트를 구현하는 것은 일반적으로 구조를 사용하는데, 첫째, 연결리스트의 구조적 특성을 통해 구조의 구성원을 유추할 수 있다. 단일 연결 리스트는 이전 노드를 통해서만 다음 노드를 찾을 수 있으며 단방향입니다. 각 노드에는 데이터 요소도 저장됩니다. 이것을 구현하는 방식은 포인터이므로 이전 노드에 다음 주소를 저장해야 합니다. .

typedef int SLTDateType;//方便以后修改链表类型
typedef struct STLListNode {
	SLTDateType n;
	struct STLListNode* next;
}SListNode;//减少代码的冗余

2) 단일 연결 목록에 대해 생각하기 (그런 다음 연결 목록을 찾아 연결 목록의 끝을 결정)

첫 번째는 연결리스트의 첫 번째 요소를 찾는 방법입니다. 매번 앞의 그림에서 힌트를 드렸는데요. 헤드 포인터를 사용하여 첫 번째 노드를 표시할 수 있지만 큰 메모가 있습니다. 무작정 변경할 수는 없습니다. . head의 주소, 그렇지 않으면 이 연결 목록을 찾을 수 없습니다.

그렇다면 연결리스트의 끝을 판단하는 방법은 위의 손그림을 보면 마지막 노드가 NULL 포인터를 가리키는데, 이 특징을 바탕으로 연결리스트의 끝을 판단할 수 있다.

참고: 이 두 가지 문제를 고려해야 하는 이유는 포인터의 포인팅을 엄격하게 제어하지 않으면 포인터가 열려 있지 않은 공간을 가리키는 경우 프로그램이 여러 가지 사고를 일으키거나 심지어 실패할 수도 있기 때문입니다. 달리다.

3) 단일 연결 리스트의 프로그램 구현 및 소스코드 설명

1) 연결리스트 구현을 위한 전제조건
#include<stdio.h>
#include<assert.h>
typedef int SLTDateType;//方便以后修改链表类型
typedef struct STLListNode {
	SLTDateType n;
	struct STLListNode* next;
}SListNode;//减少代码的冗余
2) 단일 연결 리스트 생성 및 초기화
SListNode* BuySListNode(SLTDateType x);
SListNode* BuySListNode(SLTDateType x) {
	SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));//分配内存空间
	assert(newnode);//防止分配失败导致的访问非法空间
	newnode->n= x;
	return newnode;//返回创建空间地址
}
3) 단일 연결 리스트 삽입 종료

void SListPushBack(SListNode** pplist, SLTDateType x);
void SListPushBack(SListNode** pplist, SLTDateType x) {//链表要传地址用二级指针接受,因为头插的时候是要改变链表的地址,是需要二级指针才能改变一级指针
	SListNode* ptemp = *pplist;
	SListNode** plist = pplist;//防止头指针丢失
	if (*plist == NULL) {
		*plist = BuySListNode(x);
		(*plist)->next = NULL;
		return;
	}//如果是空链表需要单独处理
	while ((*plist)->next != NULL) {
		*plist = (*plist)->next;
	}//找到尾节点
	(*plist)->next  = BuySListNode(x);//分配一个新空间
	(*plist)->next->next = NULL;//置空方便下次找尾结点
	*pplist = ptemp;//维持头指针
}
4) 단일 연결 리스트의 헤드 플러그

void SListPushFront(SListNode** pplist, SLTDateType x) {//要改变头指针的地址,需要二级指针
	if (*pplist == NULL) {
		*pplist = BuySListNode(x);
		(*pplist)->next = NULL;
		return;//如果链表为空,单独处理
	}
	SListNode* plist= BuySListNode(x);//分配空间
	plist->next = (*pplist);//将新结点的next指针指向原来的头指针
	*pplist = plist;//改变头指针
}
5) 단일 연결 리스트의 헤드를 삭제합니다.

void SListPopFront(SListNode** pplist){
	assert(*pplist);//判断是否为空链表,防止访问非法空间
	SListNode* plist = *pplist;
	*pplist = (*pplist)->next;//保留新头
	plist->next = NULL;//老的头指针置空,防止通过这个非法访问
	free(plist);//释放老头指针空间
}
6) 단일 연결 리스트의 tail 삭제

void SListPopBack(SListNode** pplist) {
	assert(*pplist);//判断是否为空链表,防止访问非法空间
	SListNode* plist = (*pplist);//保存头指针,防止丢失
	if (plist->next == NULL) {
		free(plist);
		plist = NULL;
	}//如果只有一个元素,直接释放
	while (plist->next->next != NULL) {
		plist = plist->next;
	}//找到尾结点
	free(plist->next);
	plist->next = NULL;//置空
}
7) 단일 연결 리스트에서 요소 찾기
SListNode* SListFind(SListNode* plist, SLTDateType x) {
	assert(plist);//判断是否为空链表,防止访问非法空间
	while (plist->next != NULL) {
		if (plist->n = x)
			return plist;//如果找到直接返回地址
		plist = plist->next;//否则下一个
	}
	return NULL;//找到了尾结点都没找到,返回空指针
}
8) 단일 연결 리스트에서 지정된 노드 뒤에 요소 삽입 및 삭제
void SListInsertAfter(SListNode* pos, SLTDateType x) {
	assert(pos);//判断是否为空链表,防止访问非法空间
	SListNode* temp = pos->next;
	pos->next = BuySListNode(x);
	pos->next->next = temp;
}
void SListEraseAfter(SListNode* pos) {
	assert(pos);//判断是否为空链表,防止访问非法空间
	assert(pos->next );//判断是否有下一个元素
	SListNode* temp = pos->next;
	pos->next = pos->next->next;//改变前一个指针的next指针,防止断层
	free(temp);
}
9) 단일 연결 리스트의 메모리 파괴
void SListDestroy(SListNode* plist) {
	assert(plist);//防止多次释放空间
	SListNode* cur = plist->next;//记录当前指针,因为当前指针释放后无法访问到下一个指针的地址
	while (cur) {
		free(plist);
		plist = cur;
		cur = plist->next;
	}
	plist = NULL;
}

2) 양방향 순환 연결 리스트를 주도하기 위한 팁(직접 구현)

단일 연결 목록과 비교할 때 선행 양방향 순환 연결 목록은 데이터를 저장하는 데 사용되지 않지만 헤드 포인터를 가리키는 데 사용되는 추가 공간을 열어 많은 작업을 단순화할 수 있습니다. 이전 노드를 가리키는 포인터가 하나 더 있고 꼬리 포인터는 더 이상 비어 있지 않지만 첫 번째 노드를 가리킵니다.

2. 큐와 스택

1) 대기열 특성

사진 속 핵산검사와 마찬가지로 대기열에 먼저 입장하시면 핵산검사를 완료하고 다른 사람들보다 먼저 퇴실하실 수 있습니다. 따라서 대기열의 특성은 선입선출입니다.

2) 스택의 특성

스택의 특성은 한 번에 하나의 돌만 꺼낼 수 있는 병에 돌을 던지는 것과 같습니다. 바닥 돌을 얻으려면 먼저 이전 돌을 모두 꺼내야 합니다. 따라서 스택의 특성은 먼저 들어가고 마지막에 나옵니다.

3) Linked List를 이용하여 Queue를 구현합니다. (소스코드 및 자세한 설명)

1) 대기열 구조 및 기능 구현 전 준비

연결 리스트와 마찬가지로 큐도 구조 포인터 방법을 사용하여 구현됩니다. 이는 연결 리스트 구현과 유사하지만 큐의 첫 번째 요소 포인터 주소와 요소 포인터의 주소를 저장하는 데 사용되는 추가 구조가 있습니다. 마지막 요소 포인터이므로 향후 다양한 기능을 구현하는 것이 편리하고 많은 코드를 절약할 수 있습니다.

#include<stdio.h>
#include<assert.h>
typedef int QDataType;//方便以后将队列修改为其他类型
typedef struct QListNode
{
	struct QListNode* _next;//下一个队列的指针
	QDataType _data;//数据元素
}QNode;//减少代码长度
typedef struct Queue
{
	QNode* _front;//队列第一个元素指针
	QNode* _rear;//队列最后一个元素指针
}Queue;
2) 대기열 초기화
void QueueInit(Queue* q) {
	q->_front = (QNode*)malloc(sizeof(QNode));//开辟空间
	q->_front->_data = -1;//数据随意初始化
	q->_rear = q->_front ;//此时只有一个元素,头和尾相等
}
3) 데이터 입력
void QueuePush(Queue* q, QDataType data) {
	q->_rear->_data = data;//从尾开始入列
	q->_rear->_next = (QNode*)malloc(sizeof(QNode));//给下一个队列分配空间
	q->_rear = q->_rear->_next;//移动尾指针
}
4) 데이터 대기열 제거
void QueuePop(Queue* q) {
	assert(q->_front!=q->_rear );//防止出列空队列
	QNode* list = q->_front ;//保存头指针,防止丢失
	while (list->_next != q->_rear) {
		list = list->_next;
	}//找到尾指针
	free(q->_rear);//释放尾指针
	q->_rear = list;//重新恢复尾指针
}
5) 대기열의 헤드 요소를 가져옵니다. 
QDataType QueueFront(Queue* q) {
	assert(q->_front != q->_rear);
	return q->_front->_data;
}
6) 대기열의 꼬리 요소를 가져옵니다.
QDataType QueueBack(Queue* q) {
	assert(q->_front != q->_rear);//判断是否有至少两个元素,防止数组越界
	QNode* list = q->_front;//保存头指针,防止丢失
	while (list->_next != q->_rear) {
		list = list->_next;
	}//找到尾结点,队列最后一个元素指针
	return list->_data;
}
7) 대기열 요소 삭제
void QueueDestroy(Queue* q) {
	while (q->_front != q->_rear) {
		QNode* list = q->_front;//利用list来销毁上一个指针,防止销毁之后找不到下一个元素
		q->_front = q->_front->_next;//头指针换为下一个元素
		free(list);//销毁
	}
	free(q->_rear);//不要忘记还落下了一个尾结点
	q->_front = NULL;
	q->_rear = NULL;//置空,防止非法访问
}

4) 스택 코드 구현(다른 아이디어 제공 및 직접 구현)

스택은 한 번에 하나의 돌만 담을 수 있는 병에 돌을 넣고 빼는 것과 같다고 전에 말씀드렸는데, 스택과 배열이 매우 비슷하다는 것을 아셨나요? 배열을 사용하여 구현하면 코드 아이디어와 코드 양이 갑자기 훨씬 작아졌습니다. 여기서는 구조와 기능을 이해하기 전의 준비사항만 설명하고 나머지는 열심히 연습하시기 바랍니다.

#include<stdio.h>
#include<assert.h>
// 支持动态增长的栈
typedef int STDataType;
typedef struct Stack
{
	STDataType* _a; //到时候用maolloc开辟空间,可以随时调节数组大小
	int _top;		// 栈顶
	int _capacity;  // 容量 
}Stack;

마지막으로, 데이터 구조를 연습하려면 많은 질문이 필요합니다.순수한 이론적 지식은 단지 종이에 적힌 이야기일 뿐이고, 실제로 구현되면 많이 바뀔 것입니다. 명심하세요: 종이에서 배운 내용은 의미가 있을 뿐이지만, 어떻게 해야 하는지 확실히 알게 될 것입니다.

추천

출처blog.csdn.net/m0_74316391/article/details/132589416