北京公車採購案 八成為電動車

北京公交集團持續落實首都空氣清淨規劃,2016年計畫採購2700輛新的公車,其中有81%將採購電動車款,以減少大眾運輸系統的碳排放量。

北京霧霾問題嚴重,改用電動車來取代汽油車是減輕空氣汙染的方法之一。2015年間,北京公交集團共置換了2306輛汽油公車為新式環保公車,其中有一半以上是新能源電動車。同時,2015年改造超過8000輛柴油公車,減少60%的氮氧化合物排放量。

在純電動車基礎設施方面,北京公交集團陸續興建吳癸電動車線路網、變電站、充電站網絡等,共有21個公車站可供純電動車充電。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

010.Kubernetes二進制部署kube-controller-manager

一 部署高可用kube-controller-manager

1.1 高可用kube-controller-manager介紹


本實驗部署一個三實例 kube-controller-manager 的集群,啟動后將通過競爭選舉機制產生一個 leader 節點,其它節點為阻塞狀態。當 leader 節點不可用時,阻塞的節點將再次進行選舉產生新的 leader 節點,從而保證服務的可用性。

為保證通信安全,本文檔先生成 x509 證書和私鑰,kube-controller-manager 在如下兩種情況下使用該證書:

  • 與 kube-apiserver 的安全端口通信;
  • 在安全端口(https,10252) 輸出 prometheus 格式的 metrics。

1.2 創建kube-controller-manager證書和私鑰

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# cat > kube-controller-manager-csr.json <<EOF
  3 {
  4   "CN": "system:kube-controller-manager",
  5   "hosts": [
  6     "127.0.0.1",
  7     "172.24.8.71",
  8     "172.24.8.72",
  9     "172.24.8.73"
 10   ],
 11   "key": {
 12     "algo": "rsa",
 13     "size": 2048
 14   },
 15   "names": [
 16     {
 17       "C": "CN",
 18       "ST": "Shanghai",
 19       "L": "Shanghai",
 20       "O": "system:kube-controller-manager",
 21       "OU": "System"
 22     }
 23   ]
 24 }
 25 EOF
 26 #創建kube-controller-manager的CA證書請求文件



解釋:

hosts 列表包含所有 kube-controller-manager 節點 IP;

CN 和 O 均為 system:kube-controller-manager,kubernetes 內置的 ClusterRoleBindings system:kube-controller-manager 賦予 kube-controller-manager 工作所需的權限。



  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# cfssl gencert -ca=/opt/k8s/work/ca.pem \
  3 -ca-key=/opt/k8s/work/ca-key.pem -config=/opt/k8s/work/ca-config.json \
  4 -profile=kubernetes kube-controller-manager-csr.json | cfssljson -bare kube-controller-manager	#生成CA密鑰(ca-key.pem)和證書(ca.pem)


1.3 分發證書和私鑰

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# for master_ip in ${MASTER_IPS[@]}
  4   do
  5     echo ">>> ${master_ip}"
  6     scp kube-controller-manager*.pem root@${master_ip}:/etc/kubernetes/cert/
  7   done


1.4 創建和分發kubeconfig


kube-controller-manager 使用 kubeconfig 文件訪問 apiserver,該文件提供了 apiserver 地址、嵌入的 CA 證書和 kube-controller-manager 證書:

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# kubectl config set-cluster kubernetes \
  4   --certificate-authority=/opt/k8s/work/ca.pem \
  5   --embed-certs=true \
  6   --server=${KUBE_APISERVER} \
  7   --kubeconfig=kube-controller-manager.kubeconfig
  8 
  9 [root@k8smaster01 work]# kubectl config set-credentials system:kube-controller-manager \
 10   --client-certificate=kube-controller-manager.pem \
 11   --client-key=kube-controller-manager-key.pem \
 12   --embed-certs=true \
 13   --kubeconfig=kube-controller-manager.kubeconfig
 14 
 15 [root@k8smaster01 work]# kubectl config set-context system:kube-controller-manager \
 16   --cluster=kubernetes \
 17   --user=system:kube-controller-manager \
 18   --kubeconfig=kube-controller-manager.kubeconfig
 19 
 20 [root@k8smaster01 work]# kubectl config use-context system:kube-controller-manager --kubeconfig=kube-controller-manager.kubeconfig
 21 
 22 [root@k8smaster01 ~]# cd /opt/k8s/work
 23 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
 24 [root@k8smaster01 work]# for master_ip in ${MASTER_IPS[@]}
 25   do
 26     echo ">>> ${master_ip}"
 27     scp kube-controller-manager.kubeconfig root@${master_ip}:/etc/kubernetes/
 28   done


1.5 創建kube-controller-manager的systemd

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# cat > kube-controller-manager.service.template <<EOF
  4 [Unit]
  5 Description=Kubernetes Controller Manager
  6 Documentation=https://github.com/GoogleCloudPlatform/kubernetes
  7 
  8 [Service]
  9 WorkingDirectory=${K8S_DIR}/kube-controller-manager
 10 ExecStart=/opt/k8s/bin/kube-controller-manager \\
 11   --profiling \\
 12   --cluster-name=kubernetes \\
 13   --controllers=*,bootstrapsigner,tokencleaner \\
 14   --kube-api-qps=1000 \\
 15   --kube-api-burst=2000 \\
 16   --leader-elect \\
 17   --use-service-account-credentials\\
 18   --concurrent-service-syncs=2 \\
 19   --bind-address=##MASTER_IP## \\
 20   --secure-port=10252 \\
 21   --tls-cert-file=/etc/kubernetes/cert/kube-controller-manager.pem \\
 22   --tls-private-key-file=/etc/kubernetes/cert/kube-controller-manager-key.pem \\
 23   --port=0 \\
 24   --authentication-kubeconfig=/etc/kubernetes/kube-controller-manager.kubeconfig \\
 25   --client-ca-file=/etc/kubernetes/cert/ca.pem \\
 26   --requestheader-allowed-names="" \\
 27   --requestheader-client-ca-file=/etc/kubernetes/cert/ca.pem \\
 28   --requestheader-extra-headers-prefix="X-Remote-Extra-" \\
 29   --requestheader-group-headers=X-Remote-Group \\
 30   --requestheader-username-headers=X-Remote-User \\
 31   --authorization-kubeconfig=/etc/kubernetes/kube-controller-manager.kubeconfig \\
 32   --cluster-signing-cert-file=/etc/kubernetes/cert/ca.pem \\
 33   --cluster-signing-key-file=/etc/kubernetes/cert/ca-key.pem \\
 34   --experimental-cluster-signing-duration=8760h \\
 35   --horizontal-pod-autoscaler-sync-period=10s \\
 36   --concurrent-deployment-syncs=10 \\
 37   --concurrent-gc-syncs=30 \\
 38   --node-cidr-mask-size=24 \\
 39   --service-cluster-ip-range=${SERVICE_CIDR} \\
 40   --pod-eviction-timeout=6m \\
 41   --terminated-pod-gc-threshold=10000 \\
 42   --root-ca-file=/etc/kubernetes/cert/ca.pem \\
 43   --service-account-private-key-file=/etc/kubernetes/cert/ca-key.pem \\
 44   --kubeconfig=/etc/kubernetes/kube-controller-manager.kubeconfig \\
 45   --logtostderr=true \\
 46   --v=2
 47 Restart=on-failure
 48 RestartSec=5
 49 
 50 [Install]
 51 WantedBy=multi-user.target
 52 EOF


1.6 分發systemd

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# for (( i=0; i < 3; i++ ))
  4   do
  5     sed -e "s/##MASTER_NAME##/${MASTER_NAMES[i]}/" -e "s/##MASTER_IP##/${MASTER_IPS[i]}/" kube-controller-manager.service.template > kube-controller-manager-${MASTER_IPS[i]}.service
  6   done						#修正相應IP
  7 [root@k8smaster01 work]# ls kube-controller-manager*.service
  8 [root@k8smaster01 ~]# cd /opt/k8s/work
  9 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
 10 [root@k8smaster01 work]# for master_ip in ${MASTER_IPS[@]}
 11   do
 12     echo ">>> ${master_ip}"
 13     scp kube-controller-manager-${master_ip}.service root@${master_ip}:/etc/systemd/system/kube-controller-manager.service
 14   done						#分發system


二 啟動並驗證

2.1 啟動kube-controller-manager 服務

  1 [root@k8smaster01 ~]# cd /opt/k8s/work
  2 [root@k8smaster01 work]# source /opt/k8s/bin/environment.sh
  3 [root@k8smaster01 work]# for master_ip in ${MASTER_IPS[@]}
  4   do
  5     echo ">>> ${master_ip}"
  6     ssh root@${master_ip} "mkdir -p ${K8S_DIR}/kube-controller-manager"
  7     ssh root@${master_ip} "systemctl daemon-reload && systemctl enable kube-controller-manager && systemctl restart kube-controller-manager"
  8   done


2.2 檢查kube-controller-manager 服務

  1 [root@k8smaster01 ~]# source /opt/k8s/bin/environment.sh
  2 [root@k8smaster01 ~]# for master_ip in ${MASTER_IPS[@]}
  3   do
  4     echo ">>> ${master_ip}"
  5     ssh root@${master_ip} "systemctl status kube-controller-manager|grep Active"
  6   done



2.3 查看輸出的 metrics

  1 [root@k8smaster01 ~]# curl -s --cacert /opt/k8s/work/ca.pem --cert /opt/k8s/work/admin.pem --key /opt/k8s/work/admin-key.pem https://172.24.8.71:10252/metrics |head


注意:以上命令在 kube-controller-manager 節點上執行。

2.4 查看權限

  1 [root@k8smaster01 ~]# kubectl describe clusterrole system:kube-controller-manager



ClusteRole system:kube-controller-manager 的權限很小,只能創建 secret、serviceaccount 等資源對象,各 controller 的權限分散到 ClusterRole system:controller:XXX 中。

當在 kube-controller-manager 的啟動參數中添加 –use-service-account-credentials=true 參數,這樣 main controller 會為各 controller 創建對應的 ServiceAccount XXX-controller。內置的 ClusterRoleBinding system:controller:XXX 將賦予各 XXX-controller ServiceAccount 對應的 ClusterRole system:controller:XXX 權限。

  1 [root@k8smaster01 ~]# kubectl get clusterrole|grep controller



如deployment controller:

  1 [root@k8smaster01 ~]# kubectl describe clusterrole system:controller:deployment-controller


2.5 查看當前leader

  1 [root@k8smaster01 ~]# kubectl get endpoints kube-controller-manager --namespace=kube-system  -o yaml



kubelet 認證和授權:https://kubernetes.io/docs/admin/kubelet-authentication-authorization/#kubelet-authorization
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

Spring 框架常用語法進行總結

 

Spring 框架常用語法進行總結:

spring框架的二大主要的功能就是IOC和AOP。

IOC: 控制反轉(依賴注入)

AOP: 面向切面編程

學習spring最好的方法就是去看官網,裏面有詳細的說明及使用原則

介紹spring 中的註解的使用,xml配置等目前在市面上面較少。

 

首先介紹Java自帶的元註解 (元註解就是 能註解到註解上的註解,能用在其他註解上的註解 )

Java5.0定義了4個標準的meta-annotation類型

@Target :

用於描述註解的範圍,即註解在哪用。它說明了Annotation所修飾的對象範圍:Annotation可被用於 packages、types(類、接口、枚舉、Annotation類型)、類型成員(方法、構造方法、成員變量、枚舉值)、方法參數和本地變量(如循環變量、catch參數)等。取值類型(ElementType)

   CONSTRUCTOR:用於描述構造器
  FIELD:用於描述域即類成員變量
  LOCAL_VARIABLE:用於描述局部變量
  METHOD:用於描述方法
  PACKAGE:用於描述包
  PARAMETER:用於描述參數
  TYPE:用於描述類、接口(包括註解類型) 或enum聲明
  TYPE_PARAMETER:1.8版本開始,描述類、接口或enum參數的聲明
  TYPE_USE:1.8版本開始,描述一種類、接口或enum的使用聲明
  
eg :

public @interface Log {
  ......
}

 

@Retention :

用於描述註解的生命周期,表示需要在什麼級別保存該註解,即保留的時間長短。取值類型RetentionPolicy)

   SOURCE:在源文件中有效(即源文件保留)
  CLASS:在class文件中有效(即class保留)
  RUNTIME:在運行時有效(即運行時保留)  
eg:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
  ......
}

 

@Documented :

用於描述其它類型的annotation應該被作為被標註的程序成員的公共API,因此可以被例如javadoc此類的工具文檔化。它是一個標記註解,沒有成員。

eg :

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
  ......
}
@Inherited :

用於表示某個被標註的類型是被繼承的。如果一個使用了@Inherited修飾的annotation類型被用於一個class,則這個annotation將被用於該class的子類。

 

 

Spring 常用的註解

在註解配置中常用的啟動方法就是:

<--在XML中啟用方法-->
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
Person bean = (Person) applicationContext.getBean("person");
System.out.println(bean);

--------------------------------------------------------------------------
   <--在註解中啟用方法-->
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
Person bean = applicationContext.getBean(Person.class);
System.out.println(bean);


getBeanNamesForType:得到當前IOC容器加載進來的bean的名稱
String[] namesForType = applicationContext.getBeanNamesForType(Person.class);
for (String name : namesForType) {
System.out.println(name);
}
@Component

組件,沒有明確的角色

@Service

在業務邏輯層使用(service層)

@Repository

在數據訪問層使用(dao層)

@Controller

在展現層使用,控制器的聲明(Controller)

@Bean

注入ioc容器中,默認是以方法的名稱作為注入容器裏面的名稱,需注意@bean可以不在配置類裏面使用,不過經過@Bean註解使用過的方法所在的類也會被加載到ioc容器裏面。

//配置類==配置文件

@Configuration

告訴Spring這是一個配置類,用在一個類的上面,配置類

@Configuration == <bean id=”person” class=”com.opendev.entity.Person”></bean>

@ComponentScan

value:指定要掃描的包,用在配置類上面,告訴程序在spring中的掃包範圍

@ComponentScans

掃描多個包還有提供掃包的自定義掃包規則

 

package com.atguigu.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.ComponentScans;

import com.atguigu.bean.Person;

//配置類==配置文件
@Configuration  //告訴Spring這是一個配置類

@ComponentScans(
value = {
@ComponentScan(value="com.atguigu",includeFilters = {
/*@Filter(type=FilterType.ANNOTATION,classes={Controller.class}),
@Filter(type=FilterType.ASSIGNABLE_TYPE,classes={BookService.class}),*/
@Filter(type=FilterType.CUSTOM,classes={MyTypeFilter.class})
},useDefaultFilters = false)
}
)
//@ComponentScan value:指定要掃描的包
//excludeFilters = Filter[] :指定掃描的時候按照什麼規則排除那些組件
//includeFilters = Filter[] :指定掃描的時候只需要包含哪些組件
//FilterType.ANNOTATION:按照註解
//FilterType.ASSIGNABLE_TYPE:按照給定的類型;
//FilterType.ASPECTJ:使用ASPECTJ表達式
//FilterType.REGEX:使用正則指定
//FilterType.CUSTOM:使用自定義規則
public class MainConfig {

//給容器中註冊一個Bean;類型為返回值的類型,id默認是用方法名作為id
@Bean("person")//聲明了注入ioc容器裏面的對象為person,默認都是以方法名作為id
public Person person01(){
return new Person("lisi", 20);
}
}

 

//類中組件統一設置。滿足當前條件,這個類中配置的所有bean註冊才能生效;

@Conditional

裏面需要寫上相應接口的實現類

@Import

導入組件,id默認是組件的全類名

spring中bean的作用域

默認是單實例的

//默認是單實例的
/**
* ConfigurableBeanFactory#SCOPE_PROTOTYPE    
* @see ConfigurableBeanFactory#SCOPE_SINGLETON  
* @see org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST request
* @see org.springframework.web.context.WebApplicationContext#SCOPE_SESSION sesssion
* @return\
* @Scope:調整作用域
* prototype:多實例的:ioc容器啟動並不會去調用方法創建對象放在容器中。
* 每次獲取的時候才會調用方法創建對象;
* singleton:單實例的(默認值):ioc容器啟動會調用方法創建對象放到ioc容器中。
* 以後每次獲取就是直接從容器(map.get())中拿,
* request:同一次請求創建一個實例
* session:同一個session創建一個實例
*
* 懶加載:
* 單實例bean:默認在容器啟動的時候創建對象;
* 懶加載:容器啟動不創建對象。第一次使用(獲取)Bean創建對象,並初始化;
*
*/
//@Scope("prototype")
@Lazy
@Bean("person")
public Person person(){
System.out.println("給容器中添加Person....");
return new Person("張三", 25);
}
spring中bean 的生命周期
/**
* bean的生命周期:
* bean創建---初始化----銷毀的過程
* 容器管理bean的生命周期;
* 我們可以自定義初始化和銷毀方法;容器在bean進行到當前生命周期的時候來調用我們自定義的初始化和銷毀方法
*
* 構造(對象創建)
* 單實例:在容器啟動的時候創建對象
* 多實例:在每次獲取的時候創建對象\
*
* BeanPostProcessor.postProcessBeforeInitialization
* 初始化:
* 對象創建完成,並賦值好,調用初始化方法。。。
* BeanPostProcessor.postProcessAfterInitialization
* 銷毀:
* 單實例:容器關閉的時候
* 多實例:容器不會管理這個bean;容器不會調用銷毀方法;
*
*
* 遍歷得到容器中所有的BeanPostProcessor;挨個執行beforeInitialization,
* 一但返回null,跳出for循環,不會執行後面的BeanPostProcessor.postProcessorsBeforeInitialization
*
* BeanPostProcessor原理
* populateBean(beanName, mbd, instanceWrapper);給bean進行屬性賦值
* initializeBean
* {
* applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
* invokeInitMethods(beanName, wrappedBean, mbd);執行自定義初始化
* applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
*}
*
*
*
* 1)、指定初始化和銷毀方法;
* 通過@Bean指定init-method和destroy-method;
* 2)、通過讓Bean實現InitializingBean(定義初始化邏輯),
* DisposableBean(定義銷毀邏輯);
* 3)、可以使用JSR250;
* @PostConstruct:在bean創建完成並且屬性賦值完成;來執行初始化方法
* @PreDestroy:在容器銷毀bean之前通知我們進行清理工作
* 4)、BeanPostProcessor【interface】:bean的後置處理器;
* 在bean初始化前後進行一些處理工作;
* postProcessBeforeInitialization:在初始化之前工作
* postProcessAfterInitialization:在初始化之後工作
*
* Spring底層對 BeanPostProcessor 的使用;
* bean賦值,注入其他組件,@Autowired,生命周期註解功能,@Async,xxx BeanPostProcessor;
*
*
*
*/
spring中的自動裝配
/**
* 自動裝配;
* Spring利用依賴注入(DI),完成對IOC容器中中各個組件的依賴關係賦值;
*
* 1)、@Autowired:自動注入:
* 1)、默認優先按照類型去容器中找對應的組件:applicationContext.getBean(BookDao.class);找到就賦值
* 2)、如果找到多個相同類型的組件,再將屬性的名稱作為組件的id去容器中查找
* applicationContext.getBean("bookDao")
* 3)、@Qualifier("bookDao"):使用@Qualifier指定需要裝配的組件的id,而不是使用屬性名
* 4)、自動裝配默認一定要將屬性賦值好,沒有就會報錯;
* 可以使用@Autowired(required=false);
* 5)、@Primary:讓Spring進行自動裝配的時候,默認使用首選的bean;
* 也可以繼續使用@Qualifier指定需要裝配的bean的名字
* BookService{
* @Autowired
* BookDao bookDao;
* }
*
* 2)、Spring還支持使用@Resource(JSR250)和@Inject(JSR330)[java規範的註解]
* @Resource:
* 可以和@Autowired一樣實現自動裝配功能;默認是按照組件名稱進行裝配的;
* 沒有能支持@Primary功能沒有支持@Autowired(reqiured=false);
* @Inject:
* 需要導入javax.inject的包,和Autowired的功能一樣。沒有required=false的功能;
* @Autowired:Spring定義的; @Resource、@Inject都是java規範
*
* AutowiredAnnotationBeanPostProcessor:解析完成自動裝配功能;
*
* 3)、 @Autowired:構造器,參數,方法,屬性;都是從容器中獲取參數組件的值
* 1)、[標註在方法位置]:@Bean+方法參數;參數從容器中獲取;默認不寫@Autowired效果是一樣的;都能自動裝配
* 2)、[標在構造器上]:如果組件只有一個有參構造器,這個有參構造器的@Autowired可以省略,參數位置的組件還是可以自動從容器中獲取
* 3)、放在參數位置:
*
* 4)、自定義組件想要使用Spring容器底層的一些組件(ApplicationContext,BeanFactory,xxx);
* 自定義組件實現xxxAware;在創建對象的時候,會調用接口規定的方法注入相關組件;Aware;
* 把Spring底層一些組件注入到自定義的Bean中;
* xxxAware:功能使用xxxProcessor;
* ApplicationContextAware==》ApplicationContextAwareProcessor;
*
*
*
*
*/

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

工信部擬規範新能源汽車廢舊動力電池綜合利用

工業和資訊化部近日就《新能源汽車廢舊動力蓄電池綜合利用行業規範條件》向社會公開徵求意見,擬要求已在禁止建設區域投產運營的廢舊動力蓄電池綜合利用企業,要在一定期限內通過“依法搬遷、轉產”等方式逐步退出。

禁止建設區域包括:國家法律、法規、規章及規劃確定或縣級以上人民政府批准的自然保護區、生態功能保護區、風景名勝區、飲用水水源保護區、基本農田保護區和其他需要特別保護的區域等。

意見稿還對廢舊動力電池綜合利用作出規範。廢舊動力蓄電池綜合利用企業應依據相關國家、行業標準,參考新能源汽車和動力蓄電池生產企業提供的拆卸、拆解技術資訊,嚴格遵循先梯級利用後再生利用的原則,提高綜合利用水準。

根據意見稿,濕法冶煉條件下,鎳、鈷、錳的綜合回收率應不低於98%;火法冶煉條件下,鎳、稀土的綜合回收率應不低於97%;不得擅自丟棄、傾倒、焚燒與填埋廢舊動力電池中的有色金屬、石墨、塑膠、橡膠、隔膜、電解液等零部件和材料。

在能源消耗方面,意見稿規定,企業應加強對拆卸、儲存、拆解、檢測和再生利用等環節的能耗管控,努力降低綜合能耗,提高能源利用效率,鼓勵企業採用先進適用的節能技術工藝及裝備。

此外,工信部同日發佈《新能源汽車廢舊動力蓄電池綜合利用行業規範公告管理暫行辦法(徵求意見稿)》,擬對新能源汽車廢舊動力蓄電池綜合利用企業實行動態管理,委託相關專業機構負責協助做好公告管理相關工作。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

【algo&ds】2.線性表

1.線性表

線性表(英語:Linear List)是由n(n≥0)個元素()a[0],a[1],a[2]…,a[n-1]組成的。

其中:

  • 數據元素的個數n定義為表的長度 = “list”.length() (”list”.length() = 0(表裡沒有一個元素)時稱為空表)
  • 將非空的線性表(n>=1)記作:(a[0],a[1],a[2],…,a[n-1])
  • 數據元素a[i](0≤i≤n-1)只是個抽象符號,其具體含義在不同情況下可以不同

一個數據元素可以由若干個數據項組成。數據元素稱為記錄,含有大量記錄的線性表又稱為文件。這種結構具有下列特點:存在一個唯一的沒有前驅的(頭)數據元素;存在一個唯一的沒有後繼的(尾)數據元素;此外,每一個數據元素均有一個直接前驅和一個直接後繼數據元素。

2.線性表的存儲結構

  • 鏈表
    • 單鏈表
      • 動態單鏈表
      • 靜態單鏈表
    • 循環鏈表
      • 單循環鏈表
      • 雙循環鏈表
    • 靜態鏈表

3.順序表

利用數組的連續存儲空間順序存放線性表的各元素

3.1結構體定義

如果需要使用自定義的結構體來維護一個順序表,通常來講結構體的元素一般是一個固定大小的數組(可用長度足夠大),以及當前數組存放的元素個數,也即數組的長度

typedef struct LNode *List;
struct LNode {
    ElementType Data[MAXSIZE];
    int Last;//記錄順序表的最後一個元素的下標
} ;
struct LNode L;
List PtrL;

訪問結構體的成員

  • 訪問下標為 i 的元素:L.Data[i] 或 PtrL->Data[i]
  • 線性表的長度:L.Last+1 或 PtrL->Last+1
  • 指針變量PtrL還可以這樣訪問兩個屬性(*PtrL).Data[i](*PtrL).Last,不過這種訪問方式並不常用

而一般來講,為了簡單,不會去維護這樣一個結構體,(因為一旦維護了這個結構體,就需要去封裝相應的函數,比如說常見的插入、刪除、查找等操作),而是直接類似下面這樣

ElementType data[MaxSize];
int length;

定義一個足夠大的數組,然後定義一個對應關聯的變量來時刻維護數組的長度。

這兩種定義方式沒有什麼區別,一種是把常用操作封裝好,方便調用,另一種則是需要時刻自己維護對應的屬性。因為順序表的結構足夠簡單,所以不定義結構體也是可以的。

3.2順序表的常見操作

為了方便,這一節內容記錄的都是在定義的結構體基礎上,封裝的常見操作。

1.初始化

List MakeEmpty( ) {
    List PtrL;
    PtrL = (List )malloc( sizeof(struct LNode) );
    PtrL->Last = -1;
    return PtrL;
}

初始化的順序表,長度為0,所以Last為-1

2.查找

int Find( ElementType X, List PtrL ) {
    int i = 0;
    while( i <= PtrL->Last && PtrL->Data[i]!= X )
        i++;
    if (i > PtrL->Last) return -1; /* 如果沒找到,返回-1 */
    else return i; /* 找到后返回的是存儲位置 */
}

查找操作比較簡單,從順序表的第一個元素(下標為0開始)開始遍歷。

還有一種更加巧妙一點的實現方式,就是引入哨兵思想。

int Find( ElementType X, List PtrL ) {
    PtrL->Data[0] = x;//順序表第一個元素就是哨兵,賦值為x
    int i = PtrL->Last;//從最後一個元素開始遍歷
    while( PtrL->Data[i]!= X )
        i--;
    return i;
}

這樣做的好處很明顯,少了邊界的判斷,可以優化時間複雜度,編碼也更加簡單。

注意:這裏把下標為0的元素設置為哨兵,則要求順序表從下標為1開始存儲。而且,函數如果沒有找到,則一定返回i=0

3.插入

看圖示應該要注意,移動的方向是從后往前移,如果從前往後移,則Data[i]=Data[i+1]=…=Data[n],因為後面的元素都被前面移過來的元素給覆蓋了。

void Insert( ElementType X, int i, List PtrL ) {
    int j;
    if ( PtrL->Last == MAXSIZE-1 ) { /* 表空間已滿,不能插入*/
        printf("表滿");
        return;
    }
    if ( i < 1 || i > PtrL->Last+2) { /*檢查插入位置的合法性*/
        printf("位置不合法");
        return;
    }
    for ( j = PtrL->Last; j >= i-1; j-- )
        PtrL->Data[j+1] = PtrL->Data[j]; /*將 ai~ an倒序向後移動*/
    PtrL->Data[i-1] = X; /*新元素插入*/
    PtrL->Last++; /*Last仍指向最後元素*/
    return;
}

為什麼這裏需要判斷順序表的空間是否已滿?

因為這個數組,是在初始化之後就固定了數組可容納的元素個數MaxSize,一旦超出,則程序下標就會越界。C++提供了動態數組vector,可以很方便的支持動態擴展數組長度,而且基本的插入刪除等操作都封裝好了,可以很方便的使用。

4.刪除

同樣的,需要注意元素移動的方向,如果從后往前移,則最後面的元素會一直覆蓋到Data[i]。

void Delete( int i, List PtrL ) {
    int j;
    if( i < 1 || i > PtrL->Last+1 ) { /*檢查空表及刪除位置的合法性*/
        printf (“不存在第%d個元素”, i );
        return ;
    }
    for ( j = i; j <= PtrL->Last; j++ )
        PtrL->Data[j-1] = PtrL->Data[j]; /*將 ai+1~ an順序向前移動*/
    PtrL->Last--; /*Last仍指向最後元素*/
    return;
}

5.排序

因為排序算法比較多,本文不展開講解,可以參考以下博文,內容包括了常見的十大排序算法的算法分析,時間複雜度和空間複雜度分析以及c實現和動圖圖解。

4.鏈表

不要求邏輯上相鄰的兩個元素物理上也相鄰;通過“鏈”建立起數據元素之間的邏輯關係。插入、刪除不需要移動數據元素,只需要修改“鏈”。

4.1單鏈表

typedef struct LNode *List;
struct LNode {
    ElementType Data;
    List Next;
};
struct Lnode L;
List PtrL;
1.求表長
int Length ( List PtrL ) {
    List p = PtrL; /* p指向表的第一個結點*/
    int j = 0;
    while ( p ) {
        p = p->Next;
        j++; /* 當前p指向的是第 j 個結點*/
    }
    return j;
}

時間複雜度O(n)

2.查找

按序查找

List FindKth( int K, List PtrL ) {
    List p = PtrL;
    int i = 1;
    while (p !=NULL && i < K ) {
        p = p->Next;
        i++;
    }
    if ( i == K ) return p;
    /* 找到第K個,返回指針 */
    else return NULL;
    /* 否則返回空 */
}

時間複雜度O(n)

按值查找

List Find( ElementType X, List PtrL ) {
    List p = PtrL;
    while ( p!=NULL && p->Data != X )
        p = p->Next;
    return p;
}

時間複雜度O(n)

3.插入

(1)先構造一個新結點,用s指向;
(2)再找到鏈表的第 i-1個結點,用p指向;
(3)然後修改指針,插入結點 ( p之後插入新結點是 s)

List Insert( ElementType X, int i, List PtrL ) {
    List p, s;
    if ( i == 1 ) { /* 新結點插入在表頭 */
        s = (List)malloc(sizeof(struct LNode)); /*申請、填裝結點*/
        s->Data = X;
        s->Next = PtrL;
        return s; /*返回新表頭指針*/
    }
    p = FindKth( i-1, PtrL ); /* 查找第i-1個結點 */
    if ( p == NULL ) { /* 第i-1個不存在,不能插入 */
        printf("參數i錯");
        return NULL;
    } else {
        s = (List)malloc(sizeof(struct LNode)); /*申請、填裝結點*/
        s->Data = X;
        s->Next = p->Next; /*新結點插入在第i-1個結點的後面*/
        p->Next = s;
        return PtrL;
    }
}
4.刪除

(1)先找到鏈表的第 i-1個結點,用p指向;
(2)再用指針s指向要被刪除的結點(p的下一個結點);
(3)然後修改指針,刪除s所指結點;
(4)最後釋放s所指結點的空間。

List Delete( int i, List PtrL ) {
    List p, s;
    if ( i == 1 ) { /* 若要刪除的是表的第一個結點 */
        s = PtrL; /*s指向第1個結點*/
        if (PtrL!=NULL) PtrL = PtrL->Next; /*從鏈表中刪除*/
        else return NULL;
        free(s); /*釋放被刪除結點 */
        return PtrL;
    }
    p = FindKth( i-1, PtrL ); /*查找第i-1個結點*/
    if ( p == NULL ) {
        printf("第%d個結點不存在", i-1);
        return NULL;
    } else if ( p->Next == NULL ) {
        printf("第%d個結點不存在", i);
        return NULL;
    } else {
        s = p->Next; /*s指向第i個結點*/
        p->Next = s->Next; /*從鏈表中刪除*/
        free(s); /*釋放被刪除結點 */
        return PtrL;
    }
}

4.2雙鏈表

雙向鏈表,又稱為雙鏈表,是的一種,它的每個數據結點中都有兩個,分別指向直接後繼和直接前驅。所以,從雙向鏈表中的任意一個結點開始,都可以很方便地訪問它的前驅結點和後繼結點。

typedef struct DuLNode {
    ElemType data;
    struct DuLNode *prior, *next;
} DuLNode, *DuLinkList;

4.3循環鏈表

4.3.1單循環鏈表

存儲結構和單鏈表相同。

typedef struct LNode {
    ElemType data;
    struct LNode *next;
} LNode, *LinkList;

// 設立尾指針的單循環鏈表的12個基本操作
void InitList(LinkList *L) { // 操作結果:構造一個空的線性表L
    *L = (LinkList)malloc(sizeof(struct LNode)); // 產生頭結點,並使L指向此頭結點
    if (!*L) // 存儲分配失敗
        exit(OVERFLOW);
    (*L)->next = *L; // 指針域指向頭結點
}

void DestroyList(LinkList *L) { // 操作結果:銷毀線性表L
    LinkList q, p = (*L)->next; // p指向頭結點
    while (p != *L) { // 沒到表尾
        q = p->next;
        free(p);
        p = q;
    }
    free(*L);
    *L = NULL;
}

void ClearList(LinkList *L) /* 改變L */ { // 初始條件:線性表L已存在。操作結果:將L重置為空表
    LinkList p, q;
    *L = (*L)->next; // L指向頭結點
    p = (*L)->next; // p指向第一個結點
    while (p != *L) { // 沒到表尾
        q = p->next;
        free(p);
        p = q;
    }
    (*L)->next = *L; // 頭結點指針域指向自身
}

Status ListEmpty(LinkList L) { // 初始條件:線性表L已存在。操作結果:若L為空表,則返回TRUE,否則返回FALSE
    if (L->next == L) // 空
        return TRUE;
    else
        return FALSE;
}

int ListLength(LinkList L) { // 初始條件:L已存在。操作結果:返回L中數據元素個數
    int i = 0;
    LinkList p = L->next; // p指向頭結點
    while (p != L) { // 沒到表尾
        i++;
        p = p->next;
    }
    return i;
}

Status GetElem(LinkList L, int i, ElemType *e) { // 當第i個元素存在時,其值賦給e並返回OK,否則返回ERROR
    int j = 1; // 初始化,j為計數器
    LinkList p = L->next->next; // p指向第一個結點
    if (i <= 0 || i > ListLength(L)) // 第i個元素不存在
        return ERROR;
    while (j < i) { // 順指針向後查找,直到p指向第i個元素
        p = p->next;
        j++;
    }
    *e = p->data; // 取第i個元素
    return OK;
}

int LocateElem(LinkList L, ElemType e, Status(*compare)(ElemType, ElemType)) { // 初始條件:線性表L已存在,compare()是數據元素判定函數
    // 操作結果:返回L中第1個與e滿足關係compare()的數據元素的位序。
    //           若這樣的數據元素不存在,則返回值為0
    int i = 0;
    LinkList p = L->next->next; // p指向第一個結點
    while (p != L->next) {
        i++;
        if (compare(p->data, e)) // 滿足關係
            return i;
        p = p->next;
    }
    return 0;
}

Status PriorElem(LinkList L, ElemType cur_e, ElemType *pre_e) { // 初始條件:線性表L已存在
    // 操作結果:若cur_e是L的數據元素,且不是第一個,則用pre_e返回它的前驅,
    //           否則操作失敗,pre_e無定義
    LinkList q, p = L->next->next; // p指向第一個結點
    q = p->next;
    while (q != L->next) { // p沒到表尾
        if (q->data == cur_e) {
            *pre_e = p->data;
            return TRUE;
        }
        p = q;
        q = q->next;
    }
    return FALSE; // 操作失敗
}

Status NextElem(LinkList L, ElemType cur_e, ElemType *next_e) { // 初始條件:線性表L已存在
    // 操作結果:若cur_e是L的數據元素,且不是最後一個,則用next_e返回它的後繼,
    //           否則操作失敗,next_e無定義
    LinkList p = L->next->next; // p指向第一個結點
    while (p != L) { // p沒到表尾
        if (p->data == cur_e) {
            *next_e = p->next->data;
            return TRUE;
        }
        p = p->next;
    }
    return FALSE; // 操作失敗
}

Status ListInsert(LinkList *L, int i, ElemType e) /* 改變L */ { // 在L的第i個位置之前插入元素e
    LinkList p = (*L)->next, s; // p指向頭結點
    int j = 0;
    if (i <= 0 || i > ListLength(*L) + 1) // 無法在第i個元素之前插入
        return ERROR;
    while (j < i - 1) { // 尋找第i-1個結點
        p = p->next;
        j++;
    }
    s = (LinkList)malloc(sizeof(struct LNode)); // 生成新結點
    s->data = e; // 插入L中
    s->next = p->next;
    p->next = s;
    if (p == *L) // 改變尾結點
        *L = s;
    return OK;
}

Status ListDelete(LinkList *L, int i, ElemType *e) /* 改變L */ { // 刪除L的第i個元素,並由e返回其值
    LinkList p = (*L)->next, q; // p指向頭結點
    int j = 0;
    if (i <= 0 || i > ListLength(*L)) // 第i個元素不存在
        return ERROR;
    while (j < i - 1) { // 尋找第i-1個結點
        p = p->next;
        j++;
    }
    q = p->next; // q指向待刪除結點
    p->next = q->next;
    *e = q->data;
    if (*L == q) // 刪除的是表尾元素
        *L = p;
    free(q); // 釋放待刪除結點
    return OK;
}

void ListTraverse(LinkList L, void(*vi)(ElemType)) { // 初始條件:L已存在。操作結果:依次對L的每個數據元素調用函數vi()
    LinkList p = L->next->next; // p指向首元結點
    while (p != L->next) { // p不指向頭結點
        vi(p->data);
        p = p->next;
    }
    printf("\n");
}

4.3.2雙循環鏈表

// 線性表的雙向鏈表存儲結構
typedef struct DuLNode {
    ElemType data;
    struct DuLNode *prior, *next;
} DuLNode, *DuLinkList;

// 帶頭結點的雙向循環鏈表的基本操作(14個)
void InitList(DuLinkList *L) {
    // 產生空的雙向循環鏈表L
    *L = (DuLinkList)malloc(sizeof(DuLNode));
    if (*L)
        (*L)->next = (*L)->prior = *L;
    else
        exit(OVERFLOW);
}

void DestroyList(DuLinkList *L) {
    // 操作結果:銷毀雙向循環鏈表L
    DuLinkList q, p = (*L)->next; // p指向第一個結點
    while (p != *L) { // p沒到表頭
        q = p->next;
        free(p);
        p = q;
    }
    free(*L);
    *L = NULL;
}

void ClearList(DuLinkList L) { // 不改變L
    // 初始條件:L已存在。操作結果:將L重置為空表
    DuLinkList q, p = L->next; // p指向第一個結點
    while (p != L) { // p沒到表頭
        q = p->next;
        free(p);
        p = q;
    }
    L->next = L->prior = L; // 頭結點的兩個指針域均指向自身
}

Status ListEmpty(DuLinkList L) {
    // 初始條件:線性表L已存在。操作結果:若L為空表,則返回TRUE,否則返回FALSE
    if (L->next == L && L->prior == L)
        return TRUE;
    else
        return FALSE;
}

int ListLength(DuLinkList L) {
    // 初始條件:L已存在。操作結果:返回L中數據元素個數
    int i = 0;
    DuLinkList p = L->next; // p指向第一個結點
    while (p != L) { // p沒到表頭
        i++;
        p = p->next;
    }
    return i;
}

Status GetElem(DuLinkList L, int i, ElemType *e) {
    // 當第i個元素存在時,其值賦給e並返回OK,否則返回ERROR
    int j = 1; // j為計數器
    DuLinkList p = L->next; // p指向第一個結點
    while (p != L && j < i) { // 順指針向後查找,直到p指向第i個元素或p指向頭結點
        p = p->next;
        j++;
    }
    if (p == L || j > i) // 第i個元素不存在
        return ERROR;
    *e = p->data; // 取第i個元素
    return OK;
}

int LocateElem(DuLinkList L, ElemType e, Status(*compare)(ElemType, ElemType)) {
    // 初始條件:L已存在,compare()是數據元素判定函數
    // 操作結果:返回L中第1個與e滿足關係compare()的數據元素的位序。
    // 若這樣的數據元素不存在,則返回值為0
    int i = 0;
    DuLinkList p = L->next; // p指向第1個元素
    while (p != L) {
        i++;
        if (compare(p->data, e)) // 找到這樣的數據元素
            return i;
        p = p->next;
    }
    return 0;
}

Status PriorElem(DuLinkList L, ElemType cur_e, ElemType *pre_e) {
    // 操作結果:若cur_e是L的數據元素,且不是第一個,則用pre_e返回它的前驅,
    // 否則操作失敗,pre_e無定義
    DuLinkList p = L->next->next; // p指向第2個元素
    while (p != L) { // p沒到表頭
        if (p->data == cur_e) {
            *pre_e = p->prior->data;
            return TRUE;
        }
        p = p->next;
    }
    return FALSE;
}

Status NextElem(DuLinkList L, ElemType cur_e, ElemType *next_e) {
    // 操作結果:若cur_e是L的數據元素,且不是最後一個,則用next_e返回它的後繼,
    // 否則操作失敗,next_e無定義
    DuLinkList p = L->next->next; // p指向第2個元素
    while (p != L) { // p沒到表頭
        if (p->prior->data == cur_e) {
            *next_e = p->data;
            return TRUE;
        }
        p = p->next;
    }
    return FALSE;
}

DuLinkList GetElemP(DuLinkList L, int i) { // 另加
    // 在雙向鏈表L中返回第i個元素的地址。i為0,返回頭結點的地址。若第i個元素不存在,
    // 返回NULL
    int j;
    DuLinkList p = L; // p指向頭結點
    if (i < 0 || i > ListLength(L)) // i值不合法
        return NULL;
    for (j = 1; j <= i; j++)
        p = p->next;
    return p;
}

Status ListInsert(DuLinkList L, int i, ElemType e) {
    // 在帶頭結點的雙鏈循環線性表L中第i個位置之前插入元素e,i的合法值為1≤i≤表長+1
    // 改進算法2.18,否則無法在第表長+1個結點之前插入元素
    DuLinkList p, s;
    if (i < 1 || i > ListLength(L) + 1) // i值不合法
        return ERROR;
    p = GetElemP(L, i - 1); // 在L中確定第i個元素前驅的位置指針p
    if (!p) // p=NULL,即第i個元素的前驅不存在(設頭結點為第1個元素的前驅)
        return ERROR;
    s = (DuLinkList)malloc(sizeof(DuLNode));
    if (!s)
        return OVERFLOW;
    s->data = e;
    s->prior = p; // 在第i-1個元素之後插入
    s->next = p->next;
    p->next->prior = s;
    p->next = s;
    return OK;
}

Status ListDelete(DuLinkList L, int i, ElemType *e) {
    // 刪除帶頭結點的雙鏈循環線性表L的第i個元素,i的合法值為1≤i≤表長
    DuLinkList p;
    if (i < 1) // i值不合法
        return ERROR;
    p = GetElemP(L, i); // 在L中確定第i個元素的位置指針p
    if (!p) // p = NULL,即第i個元素不存在
        return ERROR;
    *e = p->data;
    p->prior->next = p->next; // 此處並沒有考慮鏈表頭,鏈表尾
    p->next->prior = p->prior;
    free(p);
    return OK;
}

void ListTraverse(DuLinkList L, void(*visit)(ElemType)) {
    // 由雙鏈循環線性表L的頭結點出發,正序對每個數據元素調用函數visit()
    DuLinkList p = L->next; // p指向頭結點
    while (p != L) {
        visit(p->data);
        p = p->next;
    }
    printf("\n");
}

void ListTraverseBack(DuLinkList L, void(*visit)(ElemType)) {
    // 由雙鏈循環線性表L的頭結點出發,逆序對每個數據元素調用函數visit()
    DuLinkList p = L->prior; // p指向尾結點
    while (p != L) {
        visit(p->data);
        p = p->prior;
    }
    printf("\n");
}

4.4靜態鏈表

前面講解的都是動態鏈表,即需要指針來建立結點之間的連接關係。而對有些問題來說結點的地址是比較小的整數(例如5位數的地址),這樣就沒有必要去建立動態鏈表,而應使用方便得多的靜態鏈表。
靜態鏈表的實現原理是hash,即通過建立一個結構體數組,並令數組的下標直接表示結點的地址,來達到直接訪問數組中的元素就能訪問結點的效果。另外,由於結點的訪問非常方便,因此靜態鏈表是不需要頭結點的。靜態鏈表結點定義的方法如下:

struct Node{
    typename data;//數據域
    int next;//指針域
}node[size];

參考資料:

  • 《算法筆記》
  • 《數據結構和算法》-極客時間專欄

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

【集合系列】- 紅黑樹實現分析

一、故事的起因

JDK1.8最重要的就是引入了紅黑樹的設計(當衝突的鏈表長度超過8個的時候),為什麼要這樣設計呢?好處就是避免在最極端的情況下衝突鏈表變得很長很長,在查詢的時候,效率會非常慢。

  • 紅黑樹查詢:其訪問性能近似於折半查找,時間複雜度O(logn);
  • 鏈表查詢:這種情況下,需要遍歷全部元素才行,時間複雜度O(n);

本文主要是講解紅黑樹的實現,只有充分理解了紅黑樹,對於後面的分析才會更加順利。

簡單的說,紅黑樹是一種近似平衡的二叉查找樹,其主要的優點就是“平衡“,即左右子樹高度幾乎一致,以此來防止樹退化為鏈表,通過這種方式來保障查找的時間複雜度為log(n)。

關於紅黑樹的內容,網上給出的內容非常多,主要有以下幾個特性:

  • 1、每個節點要麼是紅色,要麼是黑色,但根節點永遠是黑色的;
  • 2、每個紅色節點的兩個子節點一定都是黑色;
  • 3、紅色節點不能連續(也即是,紅色節點的孩子和父親都不能是紅色);
  • 4、從任一節點到其子樹中每個恭弘=叶 恭弘子節點的路徑都包含相同數量的黑色節點;
  • 5、所有的恭弘=叶 恭弘節點都是是黑色的(注意這裏說恭弘=叶 恭弘子節點其實是上圖中的 NIL 節點);

在樹的結構發生改變時(插入或者刪除操作),往往會破壞上述條件3或條件4,需要通過調整使得查找樹重新滿足紅黑樹的條件。

二、調整方式

上面已經說到當樹的結構發生改變時,紅黑樹的條件可能被破壞,需要通過調整使得查找樹重新滿足紅黑樹的條件。

調整可以分為兩類:一類是顏色調整,即改變某個節點的顏色,這種比較簡單,直接將節點顏色進行轉換即可;另一類是結構調整,改變檢索樹的結構關係。結構調整主要包含兩個基本操作:左旋(Rotate Left)右旋(RotateRight)

2.1、左旋

左旋的過程是將p的右子樹繞p逆時針旋轉,使得p的右子樹成為p的父親,同時修改相關節點的引用,使左子樹的深度加1,右子樹的深度減1,通過這種做法來調整樹的穩定性。過程如下:

以jdk1.8為例,打開HashMap的源碼部分,紅黑樹內部類TreeNode屬性分析:

static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
        //指向父節點的指針
        TreeNode<K,V> parent;
        //指向左孩子的指針
        TreeNode<K,V> left;
        //指向右孩子的指針
        TreeNode<K,V> right;
        //前驅指針,跟next屬性相反的指向
        TreeNode<K,V> prev;
        //是否為紅色節點
        boolean red;
        ......
}

左旋方法rotateLeft如下:

/*
 * 左旋邏輯
 */
static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,
                                              TreeNode<K,V> p) {
            //root:表示根節點
            //p:表示要調整的節點
            //r:表示p的右節點
            //pp:表示p的parent節點
            //rl:表示p的右孩子的左孩子節點
            TreeNode<K,V> r, pp, rl;
            //r判斷,如果r為空則旋轉沒有意義
            if (p != null && (r = p.right) != null) {
                //多個等號的連接操作從右往左看,設置rl的父親為p
                if ((rl = p.right = r.left) != null)
                    rl.parent = p;
                //判斷p的父親,為空,為根節點,根節點的話就設置為黑色
                if ((pp = r.parent = p.parent) == null)
                    (root = r).red = false;
                //判斷p節點是左兒子還是右兒子
                else if (pp.left == p)
                    pp.left = r;
                else
                    pp.right = r;
                r.left = p;
                p.parent = r;
            }
            return root;
}

2.2、右旋

了解了左旋轉之後,相應的就會有右旋,邏輯基本也是一樣,只是方向變了。右旋的過程是將p的左子樹繞p順時針旋轉,使得p的左子樹成為p的父親,同時修改相關節點的引用,使右子樹的深度加1,左子樹的深度減1,通過這種做法來調整樹的穩定性。實現過程如下:

同樣的,右旋方法rotateRight如下:

/*
 * 右旋邏輯
 */
static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root,
                                               TreeNode<K,V> p) {
            //root:表示根節點
            //p:表示要調整的節點
            //l:表示p的左節點
            //pp:表示p的parent節點
            //lr:表示p的左孩子的右孩子節點
            TreeNode<K,V> l, pp, lr;
            //l判斷,如果l為空則旋轉沒有意義
            if (p != null && (l = p.left) != null) {
                //多個等號的連接操作從右往左看,設置lr的父親為p
                if ((lr = p.left = l.right) != null)
                    lr.parent = p;
                //判斷p的父親,為空,為根節點,根節點的話就設置為黑色
                if ((pp = l.parent = p.parent) == null)
                    (root = l).red = false;
                //判斷p節點是右兒子還是左兒子
                else if (pp.right == p)
                    pp.right = l;
                else
                    pp.left = l;
                l.right = p;
                p.parent = l;
            }
            return root;
}

三、操作示例介紹

3.1、插入調整過程圖解

3.2、刪除調整過程圖解

3.3、查詢過程圖解

四、總結

至此,紅黑樹的實現就基本完成了,關於紅黑樹的結構,有很多種情況,情況也比較複雜,但是整體調整流程,基本都是先調整結構然後調整顏色,直到最後滿足紅黑樹特性要求為止。整篇文章,如果有理解不當之處,歡迎指正!

五、參考

1、
2、

作者:炸雞可樂
出處:

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

上海推出新能源汽車充電樁綜合保險

近期,上海保險業與國家電網積極合作,推出充電樁綜合保險產品。該產品包括充電樁財產保險和充電樁用電安全責任保險。充電樁財產保險保額最高1萬元,充電樁用電安全責任保險保額3萬元。

合作初期,上海相關險企採取贈送方式,建立專項資金池,向新能源車主贈送推廣充電樁保險。同時也在國家電網營業廳設點,向預報樁客戶宣傳介紹充電樁綜合保險的相關產品和服務。

據瞭解,2015年7月1日起,上海正式實施《上海市電動汽車充電基礎設施建設管理暫行規定》。7月1日後,上海市民必須提供政府備案的充電服務機構出具的“充電設施已裝證明”,才能在銷售企業辦理購買新能源汽車手續,必須安裝好充電樁後才能獲得國家和地方政府補貼、並申請到新能源車免費牌照。此外,《規定》也明確充電設施所有權人應當承擔充電設施維修更新養護及侵害第三者權益責任。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

日問周刊 | 全棧面試匯總 | 第二期

勤學如春起之苗,不見其增,日有所長;輟學如磨刀之石,不見其損,日有所虧。

我在 github 上新建了一個倉庫 ,每天至少一個問題。有關全棧,graphql,devops,微服務以及軟技能,促進職業成長,歡迎交流。

以諸葛武侯的誡子書與君共勉

夫君子之行,靜以修身,儉以養德。非澹泊無以明志,非寧靜無以致遠。夫學須靜也,才須學也,非學無以廣才,非志無以成學。淫慢則不能勵精,險躁則不能治性。年與時馳,意與日去,遂成枯落,多不接世,悲守窮廬,將復何及!

【Q037】linux 有哪些發行版,你最喜歡哪一個

原文鏈接,歡迎討論:

開放問題,不過你至少得知道一個發行版…

【Q036】http 狀態碼中 301,302和307有什麼區別

原文鏈接,歡迎討論:

  • 301,Moved Permanently。永久重定向,該操作比較危險,需要謹慎操作:如果設置了301,但是一段時間后又想取消,但是瀏覽器中已經有了緩存,還是會重定向。
  • 302,Fount。臨時重定向,但是會在重定向的時候改變 method: 把 POST 改成 GET,於是有了 307
  • 307,Temporary Redirect。臨時重定向,在重定向時不會改變 method

【Q035】http 常見的狀態碼有哪些

原文鏈接,歡迎討論:

【Q034】如何實現一個 loading 動畫

原文鏈接,歡迎討論:

【Q033】如何對接口進行限流]

原文鏈接,歡迎討論:

一般採用漏桶算法:

  1. 漏桶初始為空
  2. API 調用是在往漏桶里注水
  3. 漏桶會以一定速率出水
  4. 水滿時 API 拒絕調用

可以使用 redis 的計數器實現

  1. 計數器初始為空
  2. API 調用計數器增加
  3. 給計數器設置過期時間,隔段時間清零,視為一定速率出水
  4. 計數器達到上限時,拒絕調用

當然,這隻是大致思路,這時會有兩個問題要注意

  1. 最壞情況下的限流是額定限流速率的2倍
  2. 條件競爭問題

不過實際實現時注意以下就好了(話說一般也是調用現成的三方庫做限流…),可以參考我以前的文章

【Q032】js 中什麼是 softbind,如何實現

原文鏈接,歡迎討論:

【Q031】js 中如何實現 bind

原文鏈接,歡迎討論:

最簡單的 bind 一行就可以實現,而在實際面試過程中也不會考察你太多的邊界條件

Function.prototype.fakeBind = function(obj) {
  return (...args) => this.apply(obj, args)
}

測試一下

function f (arg) {
  console.log(this.a, arg)
}

// output: 3, 4
f.bind({ a: 3 })(4)

// output: 3, 4
f.fakeBind({ a: 3 })(4)

【Q030】linux 中如何打印所有網絡接口

原文鏈接,歡迎討論:

ifconfig

ifconfig 是最簡單最常用,但是打印信息太多了

$ ifconfig

netstat

netstatip 也挺好用,特別是它們還可以打印路由表

$ netstat -i

ip

$ ip link

$ ip addr

【Q029】websocket 如何向特定的用戶組推送消息

redis 處維護一個對象,記錄每個 group 所對應的 connections/sockets

{
  'Class:201901': [student1Socket, student2Socket]
}

當 client 剛連入 server 時,便加入某個特定的組,或者叫 room,比如 student01,剛開始連入 server,可能要加入 room:Student:01Class:201901Group:10086

$ who

$ last

一圖勝千言

使用 jsonb_pretty 函數,示例如下

> select jsonb_pretty('{"a": {"b": 4}}'::jsonb)
+----------------+
| jsonb_pretty   |
|----------------|
| {              |
|     "a": {     |
|         "b": 4 |
|     }          |
| }              |
+----------------+
SELECT 1
Time: 0.018s

一個簡單的 Promise 的粗糙實現,關鍵點在於

  1. pending 時, thenable 函數由一個隊列維護
  2. 當狀態變為 resolved(fulfilled) 時,隊列中所有 thenable 函數執行
  3. resolved 時, thenable 函數直接執行

rejected 狀態同理

class Prom {
  static resolve (value) {
    if (value && value.then) {
      return value 
    }
    return new Prom(resolve => resolve(value))
  }

  constructor (fn) {
    this.value = undefined
    this.reason = undefined
    this.status = 'PENDING'

    // 維護一個 resolve/pending 的函數隊列
    this.resolveFns = []
    this.rejectFns = []

    const resolve = (value) => {
      // 注意此處的 setTimeout
      setTimeout(() => {
        this.status = 'RESOLVED'
        this.value = value
        this.resolveFns.forEach(({ fn, resolve: res, reject: rej }) => res(fn(value)))
      })
    }

    const reject = (e) => {
      setTimeout(() => {
        this.status = 'REJECTED'
        this.reason = e
        this.rejectFns.forEach(({ fn, resolve: res, reject: rej }) => rej(fn(e)))
      })
    }

    fn(resolve, reject)
  }


  then (fn) {
    if (this.status === 'RESOLVED') {
      const result = fn(this.value)
      // 需要返回一個 Promise
      // 如果狀態為 resolved,直接執行
      return Prom.resolve(result)
    }
    if (this.status === 'PENDING') {
      // 也是返回一個 Promise
      return new Prom((resolve, reject) => {
        // 推進隊列中,resolved 后統一執行
        this.resolveFns.push({ fn, resolve, reject }) 
      })
    }
  }

  catch (fn) {
    if (this.status === 'REJECTED') {
      const result = fn(this.value)
      return Prom.resolve(result)
    }
    if (this.status === 'PENDING') {
      return new Prom((resolve, reject) => {
        this.rejectFns.push({ fn, resolve, reject }) 
      })
    }
  }
}

Prom.resolve(10).then(o => o * 10).then(o => o + 10).then(o => {
  console.log(o)
})

return new Prom((resolve, reject) => reject('Error')).catch(e => {
  console.log('Error', e)
})

首參不一樣,直接上 API

React.cloneElement(
  element,
  [props],
  [...children]
)

React.createElement(
  type,
  [props],
  [...children]
)

它一般可以使用第三方庫 來實現,源碼很簡單,可以讀一讀

主要有兩個要點

  1. 選中
  2. 複製

選中

選中主要利用了

選中的代碼如下

const selection = window.getSelection();
const range = document.createRange();

range.selectNodeContents(element);
selection.removeAllRanges();
selection.addRange(range);

selectedText = selection.toString();

取消選中的代碼如下

window.getSelection().removeAllRanges();

它有現成的第三方庫可以使用:

複製

複製就比較簡單了,execCommand

document.exec('copy')

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

科學警訊:熱浪使熊蜂瀕臨滅絕 連帶影響農糧產量

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

斷電惹民怨 南非拚能源轉型

摘錄自2020年2月16日聯合新聞網報導

南非總統拉瑪佛沙日前表示,大量依賴煤炭的南非將會轉型使用更多再生能源,減少嚴重衝擊重建經濟努力的斷電現象。不過,他也警告,近期可能會有更多斷電發生。

南非民眾對今年盛夏分區斷電感到憤怒,投資人也感到擔憂。根據南非能源部,南非約77%電力依賴燃煤火力發電,部分民眾對於官員將停電歸咎於「濕煤」覺得傻眼。

拉瑪佛沙警告,停電將會持續,電力公司Eskom正在進行必要的改變,包括拖延已久的維修。他說,「在未來幾個月,隨著Eskom努力恢復其運營能力,我們將採取措施,從根本上改變我國能源生產軌跡」。

南非政府的解決辦法之一是,允許商業和工業用戶自行發電,並且允許地方政府向獨立發電商購買電力。南非也將向現有的風力和太陽能發電廠購買更多能源。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!