数据结构基础之链表

重温四大基础数据结构:数组、链表、队列和栈 前言 本文收录于专辑:http://dwz.win/HjK,点击解锁更多数据结构与算法的知识。 你好,我是彤哥,一个每天爬二十六层楼还不忘读源码的硬核男人。 数组、链表、队列、栈,是数据结构中最基础的四大结构,数组和链表更是基础中的基础,后续所有复杂的数据结构都是在它们的基础上演变而来的。 本节,我们就来重温这四大结构。 数组 关于数组,大家都比较熟悉了。 它是一种线性数据结构,使用一组连续的内存空间存储一组具有相同类型的数据。 这个概念中有三个关键词:线性、连续、相同类型。 线性,表示没有分叉,任. 阅读详情

链表

typedef int ELEM_TYPE;
//单链表有效节点的的结构体设计
typedef struct Node
{
	//数据域
	ELEM_TYPE data;
	//指针域
	struct Node* next;
}Node, * PNode;

1.单链表

链表由一系列不再内存中相连的组织构成,每个节点均包含有数据元素和指向下一个节点的指针,即next指针,最后一个节点的next指向NULL,如图

在这里插入图片描述

1.单链表的初始化

把接收到的节点head的指针域置空,这是头节点

void Init_List(struct Node* head)
{
    assert(NULL != head);
    if(NULL == head) exit(EXIT_FAILURE);
    head->next = NULL;
}
2.插入数据
//头插法
bool Insert_head(struct Node* head,ELEM_TYPE val)
{
    assert(NULL != head);
    if(NULL == head) exit(EXIT_FAILURE);
    //1.malloc新节点
    Node* pnewnode = (Node*)malloc(sizeof(Node));
    //判断申请是否成功
    if(NULL == pnewnode) exit(EXIT_FAILURE);
    pnewnode->data = val;/给这个新节点赋值,即要插入的值
    //2.插入
    pnewnode->next = head->next;
    head->next = pnewnode;
    return true;
}

//尾插法
bool Insert_tail(struct Node* head,ELEM_TYPE val)
{
    assert(NULL != head);
    if(NULL == head) exit(EXIT_FAILURE);
    //1.malloc新节点
    Node* pnewnode = (Node*)malloc(sizeof(Node));
    //判断申请是否成功
    if(NULL == pnewnode) exit(EXIT_FAILURE);
    pnewnode->data = val;
    //2.找到合适的插入位置
    Node* p head;
    while(p->next != NULL)
        p = p->next;
    //3.插入
	pnewnode->next = p->next;
    p->next = pnewnode;
    return true;
}

//按位置插
//插入到第pos个元素的后面
bool Insert_pos(struct Node* head,ELEM_TYPE val,int pos)
{
    assert(NULL != head);
    if(NULL == head) exit(EXIT_FAILURE);
    //1.malloc新节点
    Node* pnewnode = (Node*)malloc(sizeof(Node));
    //判断申请是否成功
    if(NULL == pnewnode) exit(EXIT_FAILURE);
    pnewnode->data = val;
    //2.找到合适的位置
	Node* p = head;
    for(int i = 0 ; i < pos ; i++)//这里没有=
    {
        p = p->next;
    }
    
    pnewnode->next = p->next;
    p->next = pnewnode;
    return true;
}
3.删除数据
//头删
bool Del_head(struct Node* head)
{
    assert(NULL != head);
    if(NULL == head) exit(EXIT_FAILURE);
    //判断当前链表是否为空
	if(Is_Empty(head)) return false;
    //直接跨越待删除节点 然后释放内存
    Node* p = head->next;
    head->next = p->next;
    free(p);
    p = NULL;//避免悬空指针的出现
    return true;
}

//尾删
bool Del_tail(struct Node* head)
{
    assert(NULL != head);
    if(NULL == head) exit(EXIT_FAILURE);
    //判空
    if(Is_Empty(head)) return false;
    //申请临时指针p,让其指向待删除节点的前驱
    Node* p = head;
    while(p->next->next != NULL)
    {
        p = p->next;
    }
    //申请临时指针q,让其指向待删除节点
    Node* q = p->next;
    //跨越指向+释放内存
    p->next = q->next;
    free(q);
    q = NULL;
    return true;
}

//按位置删
bool Del_pos(struct Node* head,int pos)
{
    //这里的pos类似于数组下标,第一个元素的pos = 0
    assert(NULL != head);
    assert(pos >= 0 && pos < Get_Length(head));
    if(NULL == head) exit(EXIT_FAILURE);
    //申请两个临时指针p和q,让其分别指向待删除节点的前驱和待删除节点
    Node* p = head;
    Node* q = head;
    for(int i = 0 ; i < pos ; i++)
        p = p->next;
    q = p->next;
    //跨越指向+释放内存
    p->next = q->next;
    free(q) ;
    q = NULL;
    return true;
}

//按值删(删除val第一次出现的节点)
bool Del_val(struct Node* head,ELEM_TYPE val)
{
    assert(NULL != head);
    if(NULL == head) exit(EXIT_FAILURE);
    //判空
    if(Is_Empty(head)) return false;
    //调用Search函数,查找当前值val是否存在,并用临时指针q接收
    Node* q = Search(head,val);
    if(q == NULL) return false;
    //申请临时指针p,指向q的前驱
    Node* p head;
    while(p->next != q)
        p = p->next;
    //跨越指向+释放
    p->next = q->next;
    free(q);
    q = NULL;
    return true;
}

//按值删(删除所有val值)
bool Del_all_val(struct Node* head,ELEM_TYPE val)
{
    assert(NULL != head);
    if(NULL == head) exit(EXIT_FAILURE);
    if(Is_Empty(head)) return false;
    //初始化双指针
    Node* prev = head;//前驱指针
    Node* cur = head->next;//当前指针
    //遍历链表
    while(cur != NULL)
    {
        if(cur->data == val)
        {
            prev->next = cur->next;
            free(cur);
            cur = prev->next;
        }
        else
        {
            prev = cur;
            cur = cur->next;
        }
    }
    return true;
}
4.查找
struct Node* Search(struct Node* head,ELEM_TYPE val)
{
    assert(NULL != head);
    if(NULL == head) exit(EXIT_FAILURE);
    //判空
    if(Is_Empty(head)); return NULL;
    //遍历链表
    for(Node* p = head->next; p != NULL; p = p->next)
    {
        if(p->data == val) return p;//找到了,停止循环
	}
    //没找到
    return NULL;
}
5.销毁
void Destroy(struct Node* head)
{
    //方法一:只要不是空链表,则一直头删
    /*
    while(!Is_Empty(head)) Del_head(head);
    */
    
    //方法二:申请辅助节点p,q,一直跨越节点释放
    assert(NULL != head);
    //申请指针p,让其保存头节点的下一个节点
    Node* p = head->next;
    Node* q;
    while(p != NULL)
    {
        //1.q指向p的下一个节点
        q = p->next;
        //2.释放p指向的节点
        free(p);
        //3.让p指向q指向的节点
        p = q;
    }
    //全部销毁完毕后,将头节点的指针域置空
    head->next = NULL;
}
6.判空和获取有效长度
//判空
bool Is_Empty(struct Node* head)
{
    return (head->next == NULL);
}

//获取有效值长度
int Get_Length(struct Node* head)
{
    int count = 0;
    for(Node* p = head->next; p != NULL; p = p->next)
    {
        count++;
    }
    return count;
}

2.循环链表

在单链表的基础上,让尾节点的next域指向头节点
在这里插入图片描述

//循环链表结构体设计
typedef int ELEM_TYPE;
typedef struct CNode
{
	ELEM_TYPE data;
	struct CNode* next;
}CNode, * PCNode;
1.初始化
void Init_CList(CNode* head)
{
    assert(NULL != head);
    if(NULL == head) exit(EXIT_FAILURE);
    head->next = head;//尾节点的next域指向头节点
}
2.插入
//头插法
bool Insert_head(CNode* head,ELEM_TYPE val)
{
    assert(NULL != plist);
	if (NULL == plist)
		exit(EXIT_FAILURE);
    //1.申请新节点
    CNode* pnewnode = (CNode*)malloc(sizeof(CNode));
    if(NULL == pnewnode) exit(EXIT_FAILURE);
    pnewnode->data = val;
    //2.插入
    pnewnode->next = head->next;//让尾指向头
    head->next = pnewnode;
    return true;
}

//尾插法
bool Insert_tail(CNode* head,ELEM_TYPE val)
{
    assert(NULL != plist);
	if (NULL == plist)
		exit(EXIT_FAILURE);
    //1.申请新节点
    CNode* pnewnode = (CNode*)malloc(sizeof(CNode));
    if(pnewnode == NULL) exit(EXIT_FAILURE);
    pnewnode->data = val;
    //2.找到合适的插入位置
    CNode* p =head;
    while(p->next != head)//这里不再是p->next != NULL
    {
        p = p->next;
    }
    //3.插入
	pnewnode->next = p->next;
    p->next = pnewnode;
    return true;
}

//按位置插入
bool Insert_pos(CNode* head,ELEM_TYPE val,int pos)
{
    assert(NULL != plist);
	if (NULL == plist)
		exit(EXIT_FAILURE);
    //1.申请新节点
    CNode* pnewnode = (CNode*)malloc(sizeof(CNode));
    if(pnewnode == NULL) exit(1);//等同于exit(EXIT_FAILURE)
    pnewnode->data = val;
    //2.找到合适位置
    CNode* p = head;
    for(int i = 0 ; i < pos ; i++)
    {
        p = p->next;
    }
    //3.插入
    pnewnode->next = p->next;
    p->next = pnewnode;
    
    
}
3.删除
//头删
bool Del_head(CNode* head,ELEM_TYPE val)
{
    assert(NULL != head);
    if(NULL == head) exit(1);
    //判空
    if(Is_Empty(head)) return false;
    //跨越待删除节点
    CNode* p = head->next;
    head->next = p->next;
    free(p);
    p = NULL;
    return true;
}

//尾删
bool Del_tail(CNode* head,ELEM_TYPE val)
{
    assert(NULL != head);
    if(NULL == head) exit(1);
    //判空
    if(Is_Empty(head)) return false;
    //1.申请临时指针p,让其指向待删除节点前驱
    CNode* p = head;
    while(p->next->next != head)
        p = p->next;
    //2.申请临时指针q,指向待删除节点
    CNode* q = p->next;
    //3.跨越指向+释放内存
    p->next = q->next;
    free(q);
    q = NULL;
    return true;
	
}

//按位置删
bool Del_pos(CNode* head,ELEM_TYPE,int pos)
{
    //这里的pos类似于数组下标,第一个元素的pos = 0
    assert(NULL != plist);
	assert(pos >= 0 && pos < Get_Length(plist));
	if (NULL == plist)
		exit(EXIT_FAILURE);
    //1.申请两个临时指针p和q,让其分别指向待删除节点的前驱和待删除节点
    CNode* p = head;
    CNode* q = head;
    for(int i = 0; i < pos; i++)
        p = p->next;
    q = p->next;
    //2.跨越待删除节点+释放内存
    p->next = q->next;
    free(q);
    q = NULL;
    return true;
}

//按值删(删除第一次出现val的节点)
bool Del_val(CNode* head,ELEM_TYPE val)
{
    assert(NULL != plist);
	if (NULL == plist)
		exit(EXIT_FAILURE);
    //判空
    if(Is_Empty(head)) return false;
    //1.调用Search函数,查找当前值val是否存在,用临时指针q接收
    CNode* q = Search(head,val);
    if(q == NULL) return false;
    //2.申请临时指针p,让其指向q的前驱
	CNode* p = head;
    while(p->next != q)
        p = p->next;
    //3.跨越指向+释放内存
    p->next = q->next;
    free(q);
    q = NULL;
    return true;
}

//按值删(删除所有val值节点)
bool Del_all_val(CNode* head,ELEM_TYPE val)
{
    assert(NULL != plist);
	if (NULL == plist)
		exit(EXIT_FAILURE);
    //判空
    if(Is_Empty(head)) return false;
    CNode* p = head;
    CNode* q = head;
    //遍历链表
    //指针p和q分别指向val的前驱和val(如果有),方便跨越指向待删除节点
    while(p->next != head)
    {
        q = p->next;
        if(q->data == val)//找到了,跨越待删除节点并释放
        {
            p->next = q->next;
            free(q);
            q = NULL;
        }
        else p = q;//没找到,p,q都往前走一步
    }
    return true;
}
4.查找
CNode* Search(CNode* head, ELEM_TYPE val)
{
	for (CNode* p = head->next; p != head; p = p->next)
	{
		if (p->data == val)
			return p;
	}
	return NULL;
}
5.销毁
void Destroy(CNode* head)
{
    assert(NULL != head);
    //1.申请临时指针p,让其保存头节点的指针域,也就是第一个有效节点
    CNode* p = head->next;
    //2.申请临时指针q
    CNode* q;
    //3.遍历节点
	while(p != head)
    {
        //1.q指向p的下一个节点
        q = p->next;
        //2.释放p
        free(p);
        //3.让p指向q
        p = q;
    }
    //4.节点全部销毁完毕,别忘了把辅助节点的指针域置空
	head->next = NULL;
}
6.判空+获取有效值长度
//判空
bool Is_Empty(CNode* head)
{
	return head == head->next;
}

//获取有效值长度
int Get_Length(CNode* head)
{
	int count = 0;
	for (CNode* p = head->next; p != h; p = p->next)
	{
		count++;
	}
	return count;
}

3.双向链表

在单链表的基础上,多加上一个prior域,让其指向自己的前驱,这样,每个节点既能指向后继(next)节点,又可以指向前驱(prior)节点

在这里插入图片描述

typedef int ELEM_TYPE;
//双向链表的结构体设计
typedef struct DNode
{
	ELEM_TYPE data;
	DNode* next;
	DNode* prior;
}DNode, * PDNode;
1.初始化
void Init_Double_List(DNode* head)
{
    assert(head != NULL);
    if(NULL == head) exit(1);
    head->next = NULL;
    head->prior = NULL;
}
2.插入
1.头插法

在这里插入图片描述

void Insert_head(DNode* head,ELEM_TYPE val)
{
    assert(NULL != head);
    if(NULL == head) exit(1);
    
    //申请新节点
	DNode* pnewnode = (DNode*)malloc(sizeof(DNode));
    if(NULL == pnewnode) exit(1);
    pnewnode->data = val;
    
    //插入
    //先修改申请节点的两个指针域,再修改插入位置的右边节点指针域,最后修改插入位置的左边节点的指针域
    pnewnode->next = head->next; // 1
    pnewnode->prior = head; //2
    //空链表不存在节点,所有不需要将其前驱指向pnewnode
    if(!Is_Empty(head))
        pnewnode->next->prior = pnewnode;//3
    head->next = pnewnode;//4
    return true;
    
}
2.尾插法

在这里插入图片描述

bool Insert_tail(PDNode head,ELEM_TYPE val)
{
    //任何情况下都是修改三个指针域,也即待插入节点的两个域和上一个节点的next域
    assert(NULL != head);
    if(NULL == head) exit(1);
    
    //1.申请新节点
    DNode* pnewnode = (DNode*)malloc(sizeof(DNode));
    if(NULL == pnewnode) exit(1);
    pnewnode->data = val;
    
    //2.找插入位置
    DNode* p = head;
    while(p->next != NULL)
        p = p->next;
    //3.插入
    pnewnode->next = p->next;
    pnewnode->prior = p;
    p->next = pnewnode;
    return true;
}
3.按位置插

在这里插入图片描述

bool Insert_pos(DNode* head,ELEM_TYPE val,int pos)
{
    assert(NULL != head);
    assert(pos >= 0 && pos <= Get_Length(head));
    if(NULL == head) exit(1);
    //对pos判断,如果pos=0,则调用头插函数
    //			如果pos=length,则调用尾插函数
    if(pos == 0) return Insert_head(head,val);
    if(pos == Get_Length(head)) return Insert_tail(head,val);
    
    //1.申请新节点
    DNode* pnewnode = (DNode*)malloc(sizeof(DNode));
    if(NULL == pnewnode) exit(1);
    pnewnode->data = val;
    //2.找插入位置
	DNode* p = head;
    for(int i = 0; i < pos; i++)
        p = p->next;
    //3.插入
    pnewnode->next = p->next;//1
    pnewnode->prior = p;//2
    pnewnode->next->prior = pnewnode;//3
    p->next = pnewnode;//4
}
3.删除
1.头删
bool Del_head(DNode* head)
{
    //普通情况:只需要修改两个指针域
    //特殊情况:只有一个节点,此时只需要修改一个指针域
	assert(NULL != head);
    if(NULL == head) exit(1);
    //判空
    if(Is_Empty(head)) return false;
    //1.申请新节点
    DNode* pnewnode = (DNode*)malloc(sizeof(DNode));
    if(NULL == pnewnode) exit(1);
    //2.申请临时指针q,让其分别指向待删除节点
    DNode* q = head->next;
    //3.跨越指向+释放内存
    head->next = q->next;
    //如果链表节点大于1,那么还需要将后面节点指向头节点以达到双向链表
    if(q->next != NULL)
        q->next->prior = head;
    free(q);
    q = NULL;
    return true;
    
}
2.尾删

尾删只需要修改一个指针域,也就是倒数第二个节点的next域

bool Del_tail(DNode* head)
{
    assert(head != NULL);
    if(NULL == head) exit(1);
    //判空
	if(Is_Empty(head)) return false;
    //申请新节点
	DNode* pnewnode = (DNode*)malloc(sizeof(DNode));
    //2.申请两个临时变量p和q,让其分别指向待删除节点的前驱和待删除节点
    DNode* q = head;
    while(q->next != NULL)
        q = q->next;
    DNode* p = q->pripr;
    //3.跨越指向+释放内存
    p->next = q->next;
    free(q);
    q = NULL;
    return true;
}
3.按位置删
bool Del_Pos(DNode* head,int pos)
{
    //这里的pos类似于数组下标,第一个元素的pos = 0
    assert(NULL != head);
    assert(pos >= 0 && pos < Get_Length(head));//有这一步就不需要再判空了
    if(NULL == head) exit(1);
    //申请新节点
    DNode* pnewnode = (DNode*)malloc(sizeof(DNode));
    if(NULL == pnewnode) exit(1);
    //提前处理头尾节点
    if(pos == 0) return Del_head(head);
    if(Pos == Get_Length(head) - 1) return Del_tail(head);
    //2.申请两个临时变量p和q,让其分别指向待删除节点的前驱和待删除节点
    DNode* p = head;
    for(int i = 0; i < pos; i++)
        p = p->next;
    DNode* q = p->next;
    //跨越指向+释放内存
    p->next = q->next;
    q->next->prior = p;
    
    free(q);
    q = NULL;
    return true;
    
}
4.按值删
//删除val第一次出现的节点
bool Del_val(DNode* head,ELEM_TYPE val)
{
    assert(NULL != head);
    if(NULL == head) exit(1);
    //判空
    if(Is_Empty(head)) return false;
    DNode* q = Search(head,val);//查找有没有这个值
    if(q == NULL) return false;
    if(head->next == q)
        return Del_head(head);
    
    DNode* p = q->prior;//待删除节点的前驱
    p->next = q->next;//跨越指向
    if(q->next != NULL) //实现双向链表 如果为空则不用
        q->next->prior = p;
    free(q);
    q = NULL;
    return true;
}

//删除所有val值节点
bool Del_val_all(DNode* head, ELEM_TYPE val)
{
	//0.assert
	assert(NULL != head);
	if (NULL == head)
		exit(EXIT_FAILURE);
	//0.5 判空
	if (Is_Empty(head))
		return false;
	DNode* p = head->next;
	while (p != NULL)
	{
		//提前保存下一个地址,cur可能会被释放
		DNode* q = p->next;
		if (p->data == val)
		{
			p->prior->next = p->next;
			if (p->next != NULL)
				p->next->prior = p->prior;
			free(p);
		}
        //下一个节点
		p = q;
	}
	return true;
}
5.判空与获取有效长度
//判空
bool Is_Empty(DNode* head)
{
    return head->next == NULL;
}

//获取有效长度
int Get_Length(DNode* head)
{
    DNode* p = head->next;
    int count = 0;
    while(p != NULL)
    {
        count++;
        p = p->next;
    }
    
}
6.销毁
void Destroy(DNode* head)
{
    assert(NULL != head);
    //创建临时指针p,让其保存头节点的指针域
    DNode* p = head->next;
    //创建临时指针q,先不赋值
    DNode* q ;
    while(p != NULL)
    {
        //1.q指向p的下一个节点
        q = p->next;
        //2.释放p所指向的节点
        free(p);
        //3.让p指向q所在的节点
        p = q;
    }
    
    //最后将头节点的指针域置空
    head->next = NULL;
}
7.查找
DNode* Search(DNode* head,ELEM_TYPE val)
{
    assert(NULL != head);
    if(NULL == head) exit(1);
    //判空
    if(Is_Empty(head)) return NULL;
    for(DNode* p = head->next; p != NULL; p = p->next)
    {
        if(p->data == val)
            return p;
    }
    return NULL;
}

4.链表相关题目

1.逆置链表

本质上还是头插法,假设头插1,3,5,2,4,遍历链表读取数据顺序为4,2,5,3,1,所以链表逆置可以理解为将原链表节点重新头插给头节点

void ReverseLink(Node* head)
{
    //保存有效链表
    Node* p = head->next;
    if(p == NULL) return ;
    //头节点断开,开始重新头插
    head->next = NULL;
    while(p != NULL)
    {
        Node* q =p->next;
        //p指向的节点进行头插
        p->next = head->next;
        head->next = p;
        p = q;
    }
}
2.求单链表的倒数第k个节点

定义两个指针pre和p都指向头节点,先让p走k步,然后pre和p同步向前走,直到p指向空,则此时pre指向的节点恰好是倒数第k个节点

也就是让pre走n-k步,p总共走n步。

p = k + (n - k) , pre = n - k;,即倒数第k个节点

Node* Get_LastK_Node(Node* head,int k)
{
    Node* p = head;
    Node* pre = head;
    for(int i = 0; i < k; i++)
    {
        p = p->next;
        if(p == NULL) return NULL;//长度不够
    }
    while(p != NULL)
    {
		pre = pre->next;
    	p = p->next;
    }
    return pre;
}
3.有序单链表合并

定义两个指针p,q分别指向两个链表的第一个有效节点

定义last指向已连接的末尾节点

void MergeLink(Node* head1,Node* head2)
{
    Node* p = head1->next;
    Node* q = head2->next;
    Node* last = head1;
    head2->next = NULL;//用不到head2了
    while(p != NULL && q != NULL)
    {
        //选择较小的节点
        if(p->data < q->data)
        {
            last->next = p;//重新指向
            p = p->next;//更新p
            last = last->next;//last始终指向末尾
        }
        else
        {
            last->next = q;
            q = q->next;
            last = last->next;
		}
    }
    //当一个节点为空之后,直接将last指向下一个非空指针,即接上非空的链表
    if(q != NULL) last->next = q;
    else last->next = p;
    
}
4.判断单链表有没有环,如果有,返回入环点
Node* Get_Entry_loop_point(Node* head)
{
    //先判断有没有环
    Node* fast = head;
    Node* slow = head;
    while(fast != NULL && fast->next != NULL)
    {
		//先各自走一步
        slow = slow->next;
        fast = fast->next->next;
        if(slow == fast)//k
        {
            //2.根据公式X = (n-1)(Y+Z)+Z
    		Node* p = head;
    		Node* q = fast;
   			 while(p != q)
    		{
        		p = p->next;
        		q = q->next;
    		}
    		return p;
        }
    }
    return NULL;//没有环
}

&& q != NULL)
{
//选择较小的节点
if(p->data < q->data)
{
last->next = p;//重新指向
p = p->next;//更新p
last = last->next;//last始终指向末尾
}
else
{
last->next = q;
q = q->next;
last = last->next;
}
}
//当一个节点为空之后,直接将last指向下一个非空指针,即接上非空的链表
if(q != NULL) last->next = q;
else last->next = p;

}


#### 4.判断单链表有没有环,如果有,返回入环点

```c
Node* Get_Entry_loop_point(Node* head)
{
    //先判断有没有环
    Node* fast = head;
    Node* slow = head;
    while(fast != NULL && fast->next != NULL)
    {
		//先各自走一步
        slow = slow->next;
        fast = fast->next->next;
        if(slow == fast)//k
        {
            //2.根据公式X = (n-1)(Y+Z)+Z
    		Node* p = head;
    		Node* q = fast;
   			 while(p != q)
    		{
        		p = p->next;
        		q = q->next;
    		}
    		return p;
        }
    }
    return NULL;//没有环
}
053、坏点校正(DPC)进阶:静态坏点标定与动态坏点检测算法对比 文章摘要: 本文深入探讨了图像传感器坏点校正(DPC)技术,重点对比静态坏点标定与动态坏点检测算法。作者结合真实案例,指出静态标定在高增益场景下的局限性,并详细解析动态检测的核心算法、参数调优及常见误检问题(如边缘/纹理干扰)。提出“静态为主+动态为辅”的混合方案,强调阈值需随增益、亮度和温度自适应调整。文末总结六条工程建议,包括避免固定阈值、优先使用中值替换、定期更新坏点表等,强调DPC是图像质量的基础防线,需结合传感器特性综合优化。 阅读详情

相关推荐

LTspice基础教程-024.参数扫描;step指令用法

step指令用于参数扫描,扫描步骤可以是线性的,对数的或列表。 下面介绍几种常用用法: 绘制一简单电阻分压电路,如下: 线性扫描 语法:.step param <Name> <Start Value> <Stop Value> <Increment> 例:.step param Res 20 100 20 意思是R2的阻值从20变化到100,...

gaoyong_wang的博客 3万+

数据结构基础:数组与链表详解

本文详细介绍了数组和链表的基本概念、操作及其应用场景,并通过C语言代码示例帮助初学者理解。我们还深入探讨了复杂链表,如循环链表和双向链表。掌握这些基础知识后,你可以进一步学习更高级的数据结构,如栈、队列、树和图等。希望这篇文章能帮助你入门数据结构,并为你的学习之旅打下坚实的基础。

qq_63520660的博客 1105

别再让COGI拖慢你的SAP接口!手把手教你配置BAPI_PRODORDCONF_CREATE_TT的同步货物移动

本文深入解析SAP生产订单报工接口中的COGI问题,通过BAPI_PRODORDCONF_CREATE_TT的同步配置实战,解决异步更新导致的性能瓶颈。文章详细介绍了增强点配置和性能验证,帮助用户彻底消除COGI堆积,提升接口效率。

weixin_29214691的博客 482

基础数据结构:链表

基础数据结构:链表 维基百科 链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而顺序表相应的时间复杂度分别是O(logn)和O(1)。 使用链表结构可以克服数组链表需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管

qq_41857875的博客 327

Deepseek+Word插件:一键搞定论文翻译与润色的学术写作新革命

本文介绍了Deepseek Word插件如何革新学术写作流程,实现论文翻译与润色的一键操作。该插件深度集成于Word环境,能智能完成中英互译、语法修正、术语统一及逻辑优化,显著提升写作效率与文本质量,是研究者的高效AI助手。

weixin_29252171的博客 513

Tessent IJTAG ICL:从规范到实践的硬件描述语言指南

本文深入解析Tessent IJTAG ICL语言在硬件测试中的应用,从规范到实践提供全面指南。通过模块化设计、网络提取与编辑等实战技巧,帮助工程师高效构建标准化测试网络,显著提升芯片测试效率与覆盖率。特别分享TDR/SIB/TAP的ICL实现方法及调试策略,适用于SoC、车载芯片等复杂场景。

weixin_33606346的博客 301

数据结构基础篇之链表

数据结构基础篇之链表

2301_76874968的博客 927

避坑指南:C#调用CodeSoft打印标签时常见的5个错误及解决方法

本文总结了C#调用CodeSoft打印标签时常见的5个核心错误及解决方案,涵盖环境配置、模板加载、变量赋值、打印机切换和资源管理。重点解析了如何避免COM对象创建失败、模板路径错误、变量名不匹配、打印机切换异常及内存泄漏等问题,帮助开发者实现稳定高效的标签打印集成。

weixin_29215815的博客 228

数据结构:数据结构基础与链表

单向链表节点通常包含数据域和指针域/* 链表存放数据的类型 *//* 链表节点类型 */// 存放数据的空间// 存放下一个节点的地址} linknode;//struct node 是结构体名称 是类型//linknode 等价于上方 是类型(link-链 node-节点)这种结构设计使得每个节点既可以存储数据,又能指向链表中的下一个节点。

m0_74911141的博客 804

使用lighttpd和开发板进行交互

本文介绍了如何在嵌入式开发板上使用Lighttpd Web服务器实现硬件交互功能。主要内容包括:Lighttpd通过CGI程序间接控制硬件的基本原理;lighttpd.conf配置文件的详细解析;开发板文件目录结构设计;用C语言编写控制GPIO的CGI程序示例;网页调用CGI实现按钮触发控制的HTML代码;常见问题调试建议。最后通过效果图展示了网页按钮点击后成功控制LED灯的实际效果,验证了Lighttpd+CGI方案在嵌入式设备Web控制中的可行性。

m0_49476241的博客 1424

基于机器学习的电视剧类型点击量数据分析研究

1. 背景介绍 1.1 流媒体平台的兴起与电视剧市场的繁荣 近年来,随着互联网技术的飞速发展,流媒体平台如Netflix、Disney+、腾讯视频等迅速崛起,成为人们观看影视内容的主要渠道。电视剧作为流媒体平台上的重要内容类型,其点击量数据蕴含着丰富的用户偏好和市场趋势信息。

AI天才研究院 1051

C语言实现基础数据结构——链表

链表是线性表中的其中一种结构,链表在物理存储结构上是非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。在链表中,每一个存储空间都是独立申请的(需要添加数据时申请),也被称为节点(结点)在双向链表中,存在头结点,即默认有一个节点,但是该节点中不存储有效数据,只存储指向上一个节点和下一个节点的地址。在双向链表中,空链表的含义是除了头结点以外没有其他任何节点(即没有存储有效数据的节点)基于链表实现顺序表学习到的通讯录项目。在双向链表中,头结点是不可以删除的。链表实现时需要熟悉的三个。

小何代码咖啡馆 1728

Python爬虫实战:58同城租房信息分析与可视化,寻找高性价比房源

随着城市化进程加快,租房需求日益增长。58同城作为国内领先的本地生活服务平台,汇聚了大量房源信息。本教程将使用最新的Python爬虫技术获取58同城租房数据,通过数据分析与可视化揭示各区域租金水平,并运用机器学习算法找出性价比高的房源。本项目将使用异步爬虫、反爬对抗技术、数据清洗、可视化分析和机器学习建模等技术栈。

2201_76125261的博客 1279

3.基础数据结构-链表

链表

weixin_67852890的博客 490

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

数据结构基础:链表专题

链表是数据结构中最基础也是最重要的线性表之一,它克服了数组固定大小的缺点,能够动态地进行内存分配。本文将全面介绍链表的概念、基本操作以及实际应用,帮助C语言初学者掌握这一重要数据结构。一、链表基础概念链表是⼀种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表的结构跟⽕⻋⻋厢相似,淡季时⻋次的⻋厢会相应减少,旺季时⻋次的⻋厢会额外增加几节。如果将⽕⻋⾥的某节⻋厢去掉/加上,其他⻋厢也不会受到影响,因为每节⻋厢都是独⽴存在的,且每节⻋厢都有⻋⻔。

htw250056的博客 1063

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

数据结构——链表基础

#pragma once typedef int DataType; #include&lt;stdio.h&gt; typedef struct Node//不带头结点的单链表 { struct Node* _pNext; DataType _data; }Node,*PNode; #include&lt;stdio.h&gt; #include&lt;assert.h&gt; ...

xiaoxiaoyu 234

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

Acwing 基础课—数据结构—单链表、双链表(数组模拟)

单链表,后续邻接表的基础——常用来存储树和图 此处使用数组模拟单链表,结构体方法在 new Struct 时非常费时。 使用e[N]存储元素,ne[N]存储next值。两者通过坐标关联。 单链表如下所示 1 单链表 题目链接 https://www.acwing.com/problem/content/description/828/ 题目大意 实现一个单链表,链表初始为空,支持三种操作: (1) 向链表头插入一个数; (2) 删除第k个插入的数后面的数; (3) 在第k个插入的数后插入一个数 现在要对该

ForTres的博客 1106

数据结构基础知识一——链表

链表 1.逻辑上有前后顺序 2.不保证逻辑前后顺序元素挨着 以下介绍几个最基础的链表操作: 首先定义Node类: class Node{ public int value; public Node next; public Node(int value){ this.value = value; this.next = null; } } 一、插入 链表的遍历 public...

Moo_Lavender的博客 303

C++基础语法:链表和数据结构

"打牢基础,万事不愁" .C++的基础语法的学习。

jllws1的博客 938

算法——数据结构基础(数组、队列、栈、链表、散链表)

算法——数据结构基础数组:顺序存储链表:随机存储栈:队列:散列表(哈希表):hash table1. 哈希函数2.散列表的读写操作:(1) 写操作:put(2) 读操作:get(3) 扩容:resize 文章结构概览 数组:顺序存储 读操作多,写操作少 1.读取(查找)元素:读取对应的下标 高效的查找元素的算法:二分查找 2.更新元素:对元素进行重新赋值 3.插入元素:尾部插入、中间插入、超范围插入 扩容问题,时间复杂度:O(n) 插入并移动元素,时间复杂度:O(n) 4.删除元素:1) 删除对应位置,

your_棒棒糖的博客 592

数据结构基础——链表的基本操作

1.链表的简介: 链表属于线性表数据结构中的一种,线性表分为顺序表和链表,顺序表可以理解为数组。 按正常方式定义一个数组时,计算机会从内存取出一块连续的地址来存放给定长度的数组;而链表则是由若干个结点所组成,(每个结点代表一个元素),且结点在内存中的存储位置通常是不连续的。 那么链表比数组方便在哪里呢? 比如我们要在一个序列中添加某一个元素,这时候使用数组就比较麻烦了,他需要将需要插入的位置后面的...

ly's Blog 798

2022-1-24 数据结构基础 链表

数据结构 1资源基础基本概念和术语复杂度内存算法链表单向链表头插法和尾插法遍历修改链表插入链表删除双向链表创建插入删除遍历循环链表初始化创建合并插入删除链式存储结构的优点: 资源 C语言网 数据结构 数据结构学习导图 c#实现的排序算法 B站数据结构学习笔记 B站数据结构课程 基础 程序=数据结构+算法 基本概念和术语 1)数据 数据(Data)是信息的载体,是可以被计算机识别,存储并加工处理的描述客观事物的信息符号的总称。数据不仅仅包括了整形,浮点数等数值类型,还包括了字符甚至声音,视频,图像等非数值的

HITORANGE的博客 713

数据结构基础之单链表

在考研开始复习数据结构时用C++实现了单链表的一些基础操作 建议考研用书王道数据结构 #include<iostream> #define ElemType int using namespace std; //单链表节点 struct List{ ElemType value; List *next; }; //初始化 List *InitList(List *L){ ...

weixin_42447968的博客 171

数据结构基础之--单链表反转

       数据结构是计算机学科的基础知识,也是程序员基础能力的体现。话不说将自己学习的一点积累分享给大家,因能力有限,如有错漏请大家批评指教。 说明: 三个指针搞定,p 工作指针, tmp指针保存p节点的下一个元 素, newh指针指向新链表头结点。 思路:每个工作周期内 p 的next指向newh,再使newh指向p,p指向tmp。当p为NULL时链表反转结束。       ...

greenhandtemporarily的专栏 249

数据结构之基础概念,顺序表,链表

数据概念 数据:对于客观事物的符号表示 数据对象:性质相同的数据元素的集合 (数组,链表) 数据元素:数据的基本单位 数据项:一个数据元素由若干个数据项组成 数据结构:研究一个个数据元素之间的关系(数组/链表/树/图) 数据的物理结构亦称为存储结构,可分为顺序,链式,索引,散列。 判断一个算法的效率,往往只需要关注操作数量的最高次项,其它次要和常数项可忽略 大O表示法: 常见时间复杂度: 时间换空间的案例: 找出数组中一个数字出现次数最多的数字。 思路1:两层for循环找出出现最多的数字和次数,时间

Charlson_S的博客 742

Java 的数据结构:从数组到链表的基础实现

数组和链表各自有其优势和不足,适用于不同的场景。数组的优势在于快速访问和空间效率,但在插入和删除操作方面有所限制。链表则在动态数据处理和插入删除方面有明显的优势,特别适合频繁修改数据的应用场景。了解它们的特性和差异,将帮助你在编程中做出合适的选择。希望这篇文章能帮助你掌握 Java 中这两种重要的数据结构,提升你的编程能力。

一名热衷于技术的全栈开发者,专注于前端与后端的全面技术探索。在这里,我将分享我在技术领域的学习与成长,助力更多开发者的进步。 1210
上一篇: 数据结构基础之顺序表
下一篇: 数据结构基础之栈
Analysis_01
博客等级 码龄3年 30粉丝 10原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值